chore(vscode): update to 1.56.0

This commit is contained in:
Akash Satheesan
2021-04-30 20:25:17 +05:30
1749 changed files with 88014 additions and 43316 deletions

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { isWeb } from 'vs/base/common/platform';
if (isWeb) {
CommandsRegistry.registerCommand('_workbench.fetchJSON', async function (accessor: ServicesAccessor, url: string, method: string) {
const result = await fetch(url, { method, headers: { Accept: 'application/json' } });
if (result.ok) {
return result.json();
} else {
throw new Error(result.statusText);
}
});
}

View File

@@ -63,6 +63,8 @@ import './mainThreadWebviewManager';
import './mainThreadWorkspace';
import './mainThreadComments';
import './mainThreadNotebook';
import './mainThreadNotebookKernels';
import './mainThreadNotebookDocumentsAndEditors';
import './mainThreadTask';
import './mainThreadLabelService';
import './mainThreadTunnelService';
@@ -71,6 +73,7 @@ import './mainThreadTimeline';
import './mainThreadTesting';
import './mainThreadSecretState';
import 'vs/workbench/api/common/apiCommands';
import 'vs/workbench/api/browser/apiCommands';
export class ExtensionPoints implements IWorkbenchContribution {

View File

@@ -6,6 +6,7 @@
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IExtHostContext, IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { reviveWorkspaceEditDto2 } from 'vs/workbench/api/browser/mainThreadEditors';
import { ILogService } from 'vs/platform/log/common/log';
@extHostNamedCustomer(MainContext.MainThreadBulkEdits)
export class MainThreadBulkEdits implements MainThreadBulkEditsShape {
@@ -13,12 +14,16 @@ export class MainThreadBulkEdits implements MainThreadBulkEditsShape {
constructor(
_extHostContext: IExtHostContext,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
@ILogService private readonly _logService: ILogService,
) { }
dispose(): void { }
$tryApplyWorkspaceEdit(dto: IWorkspaceEditDto, undoRedoGroupId?: number): Promise<boolean> {
const edits = reviveWorkspaceEditDto2(dto);
return this._bulkEditService.apply(edits, { undoRedoGroupId }).then(() => true, _err => false);
return this._bulkEditService.apply(edits, { undoRedoGroupId }).then(() => true, err => {
this._logService.warn('IGNORING workspace edit', err);
return false;
});
}
}

View File

@@ -7,7 +7,7 @@ import { Schemas } from 'vs/base/common/network';
import { isString } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CLIOutput, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
@@ -17,9 +17,10 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { ILabelService } from 'vs/platform/label/common/label';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { IOpenWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionKindController } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IExtensionManifest } from 'vs/workbench/workbench.web.api';
@@ -27,7 +28,17 @@ import { IExtensionManifest } from 'vs/workbench/workbench.web.api';
CommandsRegistry.registerCommand('_remoteCLI.openExternal', function (accessor: ServicesAccessor, uri: UriComponents | string) {
const openerService = accessor.get(IOpenerService);
openerService.open(isString(uri) ? uri : URI.revive(uri), { openExternal: true, allowTunneling: true });
return openerService.open(isString(uri) ? uri : URI.revive(uri), { openExternal: true, allowTunneling: true });
});
CommandsRegistry.registerCommand('_remoteCLI.windowOpen', function (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) {
const commandService = accessor.get(ICommandService);
return commandService.executeCommand('_files.windowOpen', toOpen, options);
});
CommandsRegistry.registerCommand('_remoteCLI.getSystemStatus', function (accessor: ServicesAccessor) {
const commandService = accessor.get(ICommandService);
return commandService.executeCommand('_issues.getSystemStatus');
});
interface ManageExtensionsArgs {
@@ -77,30 +88,27 @@ class RemoteExtensionCLIManagementService extends ExtensionManagementCLIService
private _location: string | undefined;
private readonly _extensionKindController: ExtensionKindController;
constructor(
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@ILabelService labelService: ILabelService,
@IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService,
@IExtensionManifestPropertiesService private readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,
) {
super(extensionManagementService, extensionGalleryService);
const remoteAuthority = envService.remoteAuthority;
this._location = remoteAuthority ? labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) : undefined;
this._extensionKindController = new ExtensionKindController(productService, configurationService);
}
protected get location(): string | undefined {
protected override get location(): string | undefined {
return this._location;
}
protected validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean {
if (!this._extensionKindController.canExecuteOnWorkspace(manifest)) {
protected override validateExtensionKind(manifest: IExtensionManifest, output: CLIOutput): boolean {
if (!this._extensionManifestPropertiesService.canExecuteOnWorkspace(manifest)) {
output.log(localize('cannot be installed', "Cannot install the '{0}' extension because it is declared to not run in this setup.", getExtensionId(manifest.publisher, manifest.name)));
return false;
}

View File

@@ -105,7 +105,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
disposables.add(editor.onDidDispose(remove));
disposables.add(webviewZone);
disposables.add(webview);
disposables.add(webview.onMessage(msg => this._proxy.$onDidReceiveMessage(handle, msg)));
disposables.add(webview.onMessage(msg => this._proxy.$onDidReceiveMessage(handle, msg.message)));
this._insets.set(handle, webviewZone);
}

View File

@@ -535,7 +535,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
this._commentService.updateComments(providerId, event);
}
dispose(): void {
override dispose(): void {
super.dispose();
this._workspaceProviders.forEach(value => dispose(value));
this._workspaceProviders.clear();

View File

@@ -31,13 +31,13 @@ import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/
import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopy, IWorkingCopyBackup, NO_TYPE_ID, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy';
const enum CustomEditorModelType {
Custom,
@@ -60,8 +60,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
@ICustomEditorService private readonly _customEditorService: ICustomEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IBackupFileService private readonly _backupService: IBackupFileService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
@@ -92,19 +91,19 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
}));
}
dispose() {
override dispose() {
super.dispose();
dispose(this._editorProviders.values());
this._editorProviders.clear();
}
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void {
this.registerEditorProvider(CustomEditorModelType.Text, reviveWebviewExtension(extensionData), viewType, options, capabilities, true);
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities, serializeBuffersForPostMessage: boolean): void {
this.registerEditorProvider(CustomEditorModelType.Text, reviveWebviewExtension(extensionData), viewType, options, capabilities, true, serializeBuffersForPostMessage);
}
public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void {
this.registerEditorProvider(CustomEditorModelType.Custom, reviveWebviewExtension(extensionData), viewType, options, {}, supportsMultipleEditorsPerDocument);
public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: extHostProtocol.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean, serializeBuffersForPostMessage: boolean): void {
this.registerEditorProvider(CustomEditorModelType.Custom, reviveWebviewExtension(extensionData), viewType, options, {}, supportsMultipleEditorsPerDocument, serializeBuffersForPostMessage);
}
private registerEditorProvider(
@@ -114,6 +113,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
options: extHostProtocol.IWebviewPanelOptions,
capabilities: extHostProtocol.CustomTextEditorCapabilities,
supportsMultipleEditorsPerDocument: boolean,
serializeBuffersForPostMessage: boolean,
): void {
if (this._editorProviders.has(viewType)) {
throw new Error(`Provider for ${viewType} already registered`);
@@ -133,7 +133,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
const handle = webviewInput.id;
const resource = webviewInput.resource;
this.mainThreadWebviewPanels.addWebviewInput(handle, webviewInput);
this.mainThreadWebviewPanels.addWebviewInput(handle, webviewInput, { serializeBuffersForPostMessage });
webviewInput.webview.options = options;
webviewInput.webview.extension = extension;
@@ -228,7 +228,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
const model = MainThreadCustomEditorModel.create(this._instantiationService, this._proxyCustomEditors, viewType, resource, options, () => {
return Array.from(this.mainThreadWebviewPanels.webviewInputs)
.filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)) as CustomEditorInput[];
}, cancellation, this._backupService);
}, cancellation);
return this._customEditorService.models.add(resource, viewType, model);
}
}
@@ -295,6 +295,18 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
private readonly _onDidChangeOrphaned = this._register(new Emitter<void>());
public readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event;
// TODO@mjbvz consider to enable a `typeId` that is specific for custom
// editors. Using a distinct `typeId` allows the working copy to have
// any resource (including file based resources) even if other working
// copies exist with the same resource.
//
// IMPORTANT: changing the `typeId` has an impact on backups for this
// working copy. Any value that is not the empty string will be used
// as seed to the backup. Only change the `typeId` if you have implemented
// a fallback solution to resolve any existing backups that do not have
// this seed.
readonly typeId = NO_TYPE_ID;
public static async create(
instantiationService: IInstantiationService,
proxy: extHostProtocol.ExtHostCustomEditorsShape,
@@ -303,7 +315,6 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
options: { backupId?: string },
getEditors: () => CustomEditorInput[],
cancellation: CancellationToken,
_backupFileService: IBackupFileService,
): Promise<MainThreadCustomEditorModel> {
const editors = getEditors();
let untitledDocumentData: VSBuffer | undefined;
@@ -344,7 +355,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
return this._editorResource;
}
dispose() {
override dispose() {
this.#isDisposed = true;
if (this._editable) {
@@ -651,23 +662,25 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
}
const primaryEditor = editors[0];
const backupData: IWorkingCopyBackup<CustomDocumentBackupData> = {
meta: {
viewType: this.viewType,
editorResource: this._editorResource,
backupId: '',
extension: primaryEditor.extension ? {
id: primaryEditor.extension.id.value,
location: primaryEditor.extension.location,
} : undefined,
webview: {
id: primaryEditor.id,
options: primaryEditor.webview.options,
state: primaryEditor.webview.state,
}
const backupMeta: CustomDocumentBackupData = {
viewType: this.viewType,
editorResource: this._editorResource,
backupId: '',
extension: primaryEditor.extension ? {
id: primaryEditor.extension.id.value,
location: primaryEditor.extension.location,
} : undefined,
webview: {
id: primaryEditor.id,
options: primaryEditor.webview.options,
state: primaryEditor.webview.state,
}
};
const backupData: IWorkingCopyBackup = {
meta: backupMeta
};
if (!this._editable) {
return backupData;
}

View File

@@ -157,10 +157,12 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen
}));
this._register(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => {
if (e.operation === FileOperation.MOVE || e.operation === FileOperation.DELETE) {
for (const { source } of e.files) {
if (source) {
this._modelReferenceCollection.remove(source);
const isMove = e.operation === FileOperation.MOVE;
if (isMove || e.operation === FileOperation.DELETE) {
for (const pair of e.files) {
const removed = isMove ? pair.source : pair.target;
if (removed) {
this._modelReferenceCollection.remove(removed);
}
}
}
@@ -169,7 +171,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen
this._modelTrackers = Object.create(null);
}
public dispose(): void {
public override dispose(): void {
Object.keys(this._modelTrackers).forEach((modelUrl) => {
this._modelTrackers[modelUrl].dispose();
});

View File

@@ -518,7 +518,7 @@ export class MainThreadTextEditor {
const snippetController = SnippetController2.get(this._codeEditor);
// // cancel previous snippet mode
// cancel previous snippet mode
// snippetController.leaveSnippet();
// set selection, focus editor

View File

@@ -7,7 +7,7 @@ import { SerializedError } from 'vs/base/common/errors';
import Severity from 'vs/base/common/severity';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { IExtensionService, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionService, ExtensionHostKind, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
@@ -20,6 +20,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@extHostNamedCustomer(MainContext.MainThreadExtensionService)
export class MainThreadExtensionService implements MainThreadExtensionServiceShape {
@@ -34,6 +35,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
@IHostService private readonly _hostService: IHostService,
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
@ITimerService private readonly _timerService: ITimerService,
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
) {
this._extensionHostKind = extHostContext.extensionHostKind;
}
@@ -59,25 +61,36 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
console.error(`[${extensionId}]${error.message}`);
console.error(error.stack);
}
async $onExtensionActivationError(extensionId: ExtensionIdentifier, activationError: ExtensionActivationError): Promise<void> {
if (typeof activationError === 'string') {
this._extensionService._logOrShowMessage(Severity.Error, activationError);
} else {
this._handleMissingDependency(extensionId, activationError.dependency);
}
}
async $onExtensionActivationError(extensionId: ExtensionIdentifier, data: SerializedError, missingExtensionDependency: MissingExtensionDependency | null): Promise<void> {
const error = new Error();
error.name = data.name;
error.message = data.message;
error.stack = data.stack;
private async _handleMissingDependency(extensionId: ExtensionIdentifier, missingDependency: string): Promise<void> {
const extension = await this._extensionService.getExtension(extensionId.value);
if (extension) {
const local = await this._extensionsWorkbenchService.queryLocal();
const installedDependency = local.filter(i => areSameExtensions(i.identifier, { id: missingDependency }))[0];
if (installedDependency) {
await this._handleMissingInstalledDependency(extension, installedDependency.local!);
} else {
await this._handleMissingNotInstalledDependency(extension, missingDependency);
this._extensionService._onDidActivateExtensionError(extensionId, error);
if (missingExtensionDependency) {
const extension = await this._extensionService.getExtension(extensionId.value);
if (extension) {
const local = await this._extensionsWorkbenchService.queryLocal();
const installedDependency = local.filter(i => areSameExtensions(i.identifier, { id: missingExtensionDependency.dependency }))[0];
if (installedDependency) {
await this._handleMissingInstalledDependency(extension, installedDependency.local!);
return;
} else {
await this._handleMissingNotInstalledDependency(extension, missingExtensionDependency.dependency);
return;
}
}
}
const isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
if (isDev) {
this._notificationService.error(error);
return;
}
console.error(error.message);
}
private async _handleMissingInstalledDependency(extension: IExtensionDescription, missingInstalledDependency: ILocalExtension): Promise<void> {

View File

@@ -121,7 +121,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
cancelId = buttons.length - 1;
}
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, useCustom });
const { choice } = await this._dialogService.show(severity, message, buttons, { cancelId, custom: useCustom });
return choice === commands.length ? undefined : commands[choice].handle;
}
}

View File

@@ -5,100 +5,16 @@
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { diffMaps, diffSets } from 'vs/base/common/collections';
import { Emitter } from 'vs/base/common/event';
import { IRelativePattern } from 'vs/base/common/glob';
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments';
import { URI } from 'vs/base/common/uri';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { ICellEditOperation, ICellRange, IImmediateCellEditOperation, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol';
class NotebookAndEditorState {
static compute(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): INotebookDocumentsAndEditorsDelta {
if (!before) {
return {
addedDocuments: [...after.documents].map(NotebookAndEditorState._asModelAddData),
addedEditors: [...after.textEditors.values()].map(NotebookAndEditorState._asEditorAddData),
visibleEditors: [...after.visibleEditors].map(editor => editor[0])
};
}
const documentDelta = diffSets(before.documents, after.documents);
const editorDelta = diffMaps(before.textEditors, after.textEditors);
const addedAPIEditors = editorDelta.added.map(NotebookAndEditorState._asEditorAddData);
const removedAPIEditors = editorDelta.removed.map(removed => removed.getId());
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors);
return {
addedDocuments: documentDelta.added.map(NotebookAndEditorState._asModelAddData),
removedDocuments: documentDelta.removed.map(e => e.uri),
addedEditors: addedAPIEditors,
removedEditors: removedAPIEditors,
newActiveEditor: newActiveEditor,
visibleEditors: visibleEditorDelta.added.length === 0 && visibleEditorDelta.removed.length === 0
? undefined
: [...after.visibleEditors].map(editor => editor[0])
};
}
constructor(
readonly documents: Set<NotebookTextModel>,
readonly textEditors: Map<string, IActiveNotebookEditor>,
readonly activeEditor: string | null | undefined,
readonly visibleEditors: Map<string, IActiveNotebookEditor>
) {
//
}
private static _asModelAddData(e: NotebookTextModel): INotebookModelAddedData {
return {
viewType: e.viewType,
uri: e.uri,
metadata: e.metadata,
versionId: e.versionId,
cells: e.cells.map(cell => ({
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
eol: cell.textBuffer.getEOL(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
metadata: cell.metadata
}))
};
}
private static _asEditorAddData(add: IActiveNotebookEditor): INotebookEditorAddData {
return {
id: add.getId(),
documentUri: add.viewModel.uri,
selections: add.getSelections(),
visibleRanges: add.visibleRanges,
viewColumn: undefined
};
}
}
import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector';
import { INotebookCellStatusBarItemProvider, INotebookExclusiveDocumentFilter, NotebookDataDto, TransientCellMetadata, TransientDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadNotebook)
export class MainThreadNotebooks implements MainThreadNotebookShape {
@@ -106,311 +22,42 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
private readonly _disposables = new DisposableStore();
private readonly _proxy: ExtHostNotebookShape;
private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, disposable: IDisposable }>();
private readonly _notebookProviders = new Map<string, { controller: INotebookContentProvider, disposable: IDisposable }>();
private readonly _notebookSerializer = new Map<number, IDisposable>();
private readonly _notebookKernelProviders = new Map<number, { extension: NotebookExtensionDescription, emitter: Emitter<URI | undefined>, provider: IDisposable }>();
private readonly _editorEventListenersMapping = new Map<string, DisposableStore>();
private readonly _documentEventListenersMapping = new ResourceMap<DisposableStore>();
private readonly _cellStatusBarEntries = new Map<number, IDisposable>();
private readonly _modelReferenceCollection: BoundModelReferenceCollection;
private _currentState?: NotebookAndEditorState;
private readonly _notebookCellStatusBarRegistrations = new Map<number, IDisposable>();
constructor(
extHostContext: IExtHostContext,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorService private readonly _notebookEditorService: INotebookEditorService,
@IEditorService private readonly _editorService: IEditorService,
@ILogService private readonly _logService: ILogService,
@INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService,
@INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService,
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri);
this._registerListeners();
}
dispose(): void {
this._disposables.dispose();
this._modelReferenceCollection.dispose();
// remove all notebook providers
for (const item of this._notebookProviders.values()) {
item.disposable.dispose();
}
// remove all kernel providers
for (const item of this._notebookKernelProviders.values()) {
item.emitter.dispose();
item.provider.dispose();
}
dispose(this._notebookSerializer.values());
dispose(this._editorEventListenersMapping.values());
dispose(this._documentEventListenersMapping.values());
dispose(this._cellStatusBarEntries.values());
}
async $tryApplyEdits(_viewType: string, resource: UriComponents, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise<boolean> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (!textModel) {
return false;
}
if (textModel.versionId !== modelVersionId) {
return false;
}
return textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined);
}
async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise<void> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (!textModel) {
throw new Error(`Can't apply edits to unknown notebook model: ${resource}`);
}
textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo);
}
private _registerListeners(): void {
// forward changes to dirty state
// todo@rebornix todo@mjbvz this seem way too complicated... is there an easy way to
// the actual resource from a working copy?
this._disposables.add(this._workingCopyService.onDidChangeDirty(e => {
if (e.resource.scheme !== Schemas.vscodeNotebook) {
return;
}
for (const notebook of this._notebookService.getNotebookTextModels()) {
if (isEqual(notebook.uri.with({ scheme: Schemas.vscodeNotebook }), e.resource)) {
this._proxy.$acceptDirtyStateChanged(notebook.uri, e.isDirty());
break;
}
}
}));
this._disposables.add(this._editorService.onDidActiveEditorChange(e => {
this._updateState();
}));
this._disposables.add(this._editorService.onDidVisibleEditorsChange(e => {
if (this._notebookProviders.size > 0) { // TODO@rebornix propably wrong, what about providers from another host
if (!this._currentState) {
// no current state means we didn't even create editors in ext host yet.
return;
}
// we can't simply update visibleEditors as we need to check if we should create editors first.
this._updateState();
}
}));
const handleNotebookEditorAdded = (editor: INotebookEditor) => {
if (this._editorEventListenersMapping.has(editor.getId())) {
//todo@jrieken a bug when this happens?
return;
}
const disposableStore = new DisposableStore();
disposableStore.add(editor.onDidChangeVisibleRanges(() => {
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } });
}));
disposableStore.add(editor.onDidChangeSelection(() => {
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } });
}));
disposableStore.add(editor.onDidChangeKernel(() => {
if (!editor.hasModel()) {
return;
}
this._proxy.$acceptNotebookActiveKernelChange({
uri: editor.viewModel.uri,
providerHandle: editor.activeKernel?.providerHandle,
kernelFriendlyId: editor.activeKernel?.friendlyId
});
}));
disposableStore.add(editor.onDidChangeModel(() => this._updateState()));
disposableStore.add(editor.onDidFocusEditorWidget(() => this._updateState(editor)));
this._editorEventListenersMapping.set(editor.getId(), disposableStore);
const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
this._updateState(activeNotebookEditor);
};
this._notebookEditorService.listNotebookEditors().forEach(handleNotebookEditorAdded);
this._disposables.add(this._notebookEditorService.onDidAddNotebookEditor(handleNotebookEditorAdded));
this._disposables.add(this._notebookEditorService.onDidRemoveNotebookEditor(editor => {
this._editorEventListenersMapping.get(editor.getId())?.dispose();
this._editorEventListenersMapping.delete(editor.getId());
this._updateState();
}));
const cellToDto = (cell: NotebookCellTextModel): IMainCellDto => {
return {
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
eol: cell.textBuffer.getEOL(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
metadata: cell.metadata
};
};
const handleNotebookDocumentAdded = (textModel: NotebookTextModel) => {
if (this._documentEventListenersMapping.has(textModel.uri)) {
//todo@jrieken a bug when this happens?
return;
}
const disposableStore = new DisposableStore();
disposableStore.add(textModel!.onDidChangeContent(event => {
const dto = event.rawEvents.map(e => {
const data =
e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize
? {
kind: e.kind,
versionId: event.versionId,
changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]])
}
: (
e.kind === NotebookCellsChangeType.Move
? {
kind: e.kind,
index: e.index,
length: e.length,
newIdx: e.newIdx,
versionId: event.versionId,
cells: e.cells.map(cell => cellToDto(cell as NotebookCellTextModel))
}
: e
);
return data;
});
/**
* TODO@rebornix, @jrieken
* When a document is modified, it will trigger onDidChangeContent events.
* The first event listener is this one, which doesn't know if the text model is dirty or not. It can ask `workingCopyService` but get the wrong result
* The second event listener is `NotebookEditorModel`, which will then set `isDirty` to `true`.
* Since `e.transient` decides if the model should be dirty or not, we will use the same logic here.
*/
const hasNonTransientEvent = event.rawEvents.find(e => !e.transient);
this._proxy.$acceptModelChanged(textModel.uri, {
rawEvents: dto,
versionId: event.versionId
}, !!hasNonTransientEvent);
const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata);
if (!!hasDocumentMetadataChangeEvent) {
this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata });
}
}));
this._documentEventListenersMapping.set(textModel!.uri, disposableStore);
};
this._notebookService.listNotebookDocuments().forEach(handleNotebookDocumentAdded);
this._disposables.add(this._notebookService.onDidAddNotebookDocument(document => {
handleNotebookDocumentAdded(document);
this._updateState();
}));
this._disposables.add(this._notebookService.onDidRemoveNotebookDocument(uri => {
this._documentEventListenersMapping.get(uri)?.dispose();
this._documentEventListenersMapping.delete(uri);
this._updateState();
}));
this._disposables.add(this._notebookService.onDidChangeNotebookActiveKernel(e => {
this._proxy.$acceptNotebookActiveKernelChange(e);
}));
this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => {
this._proxy.$acceptModelSaved(e);
}));
const notebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
this._updateState(notebookEditor);
}
private _updateState(focusedNotebookEditor?: INotebookEditor): void {
const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
let activeEditor = activeNotebookEditor?.hasModel() ? activeNotebookEditor.getId() : null;
const editors = new Map<string, IActiveNotebookEditor>();
const visibleEditorsMap = new Map<string, IActiveNotebookEditor>();
for (const editor of this._notebookEditorService.listNotebookEditors()) {
if (editor.hasModel()) {
editors.set(editor.getId(), editor);
}
}
this._editorService.visibleEditorPanes.forEach(editorPane => {
const notebookEditor = getNotebookEditorFromEditorPane(editorPane);
if (notebookEditor?.hasModel() && editors.has(notebookEditor.getId())) {
visibleEditorsMap.set(notebookEditor.getId(), notebookEditor);
}
});
if (!activeEditor && focusedNotebookEditor?.textModel) {
activeEditor = focusedNotebookEditor.getId();
}
const newState = new NotebookAndEditorState(new Set(this._notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap);
const delta = NotebookAndEditorState.compute(this._currentState, newState);
this._currentState = newState;
if (!this._isDeltaEmpty(delta)) {
return this._proxy.$acceptDocumentAndEditorsDelta(delta);
}
}
private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta): boolean {
if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) {
return false;
}
if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) {
return false;
}
if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) {
return false;
}
if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) {
return false;
}
if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) {
return false;
}
if (delta.newActiveEditor !== undefined) {
return false;
}
return true;
}
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: {
transientOutputs: boolean;
transientMetadata: TransientMetadata;
transientCellMetadata: TransientCellMetadata;
transientDocumentMetadata: TransientDocumentMetadata;
viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; };
}): Promise<void> {
let contentOptions = { transientOutputs: options.transientOutputs, transientMetadata: options.transientMetadata };
let contentOptions = { transientOutputs: options.transientOutputs, transientCellMetadata: options.transientCellMetadata, transientDocumentMetadata: options.transientDocumentMetadata };
const controller: IMainNotebookController = {
const controller: INotebookContentProvider = {
get options() {
return contentOptions;
},
set options(newOptions) {
contentOptions.transientMetadata = newOptions.transientMetadata;
contentOptions.transientCellMetadata = newOptions.transientCellMetadata;
contentOptions.transientDocumentMetadata = newOptions.transientDocumentMetadata;
contentOptions.transientOutputs = newOptions.transientOutputs;
},
viewOptions: options.viewOptions,
@@ -421,12 +68,6 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
transientOptions: contentOptions
};
},
resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => {
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
},
onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
},
save: async (uri: URI, token: CancellationToken) => {
return this._proxy.$saveNotebook(viewType, uri, token);
},
@@ -442,7 +83,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
this._notebookProviders.set(viewType, { controller, disposable });
}
async $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientMetadata: TransientMetadata; }): Promise<void> {
async $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata; }): Promise<void> {
const provider = this._notebookProviders.get(viewType);
if (provider && options) {
@@ -467,10 +108,10 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
const registration = this._notebookService.registerNotebookSerializer(viewType, extension, {
options,
dataToNotebook: (data: VSBuffer): Promise<NotebookDataDto> => {
return this._proxy.$dataToNotebook(handle, data);
return this._proxy.$dataToNotebook(handle, data, CancellationToken.None);
},
notebookToData: (data: NotebookDataDto): Promise<VSBuffer> => {
return this._proxy.$notebookToData(handle, data);
return this._proxy.$notebookToData(handle, data, CancellationToken.None);
}
});
this._notebookSerializer.set(handle, registration);
@@ -481,174 +122,51 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
this._notebookSerializer.delete(handle);
}
async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void> {
const emitter = new Emitter<URI | undefined>();
$emitCellStatusBarEvent(eventHandle: number): void {
const emitter = this._notebookCellStatusBarRegistrations.get(eventHandle);
if (emitter instanceof Emitter) {
emitter.fire(undefined);
}
}
async $registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, selector: NotebookSelector): Promise<void> {
const that = this;
const provider = this._notebookService.registerNotebookKernelProvider({
providerExtensionId: extension.id.value,
providerDescription: extension.description,
onDidChangeKernels: emitter.event,
selector: documentFilter,
provideKernels: async (uri: URI, token: CancellationToken): Promise<INotebookKernel[]> => {
const result: INotebookKernel[] = [];
const kernelsDto = await that._proxy.$provideNotebookKernels(handle, uri, token);
for (const dto of kernelsDto) {
result.push({
id: dto.id,
friendlyId: dto.friendlyId,
label: dto.label,
extension: dto.extension,
extensionLocation: URI.revive(dto.extensionLocation),
providerHandle: dto.providerHandle,
description: dto.description,
detail: dto.detail,
isPreferred: dto.isPreferred,
preloads: dto.preloads?.map(u => URI.revive(u)),
supportedLanguages: dto.supportedLanguages,
implementsInterrupt: dto.implementsInterrupt,
resolve: (uri: URI, editorId: string, token: CancellationToken): Promise<void> => {
this._logService.debug('MainthreadNotebooks.resolveNotebookKernel', uri.path, dto.friendlyId);
return this._proxy.$resolveNotebookKernel(handle, editorId, uri, dto.friendlyId, token);
},
executeNotebookCellsRequest: (uri: URI, cellRanges: ICellRange[]): Promise<void> => {
this._logService.debug('MainthreadNotebooks.executeNotebookCell', uri.path, dto.friendlyId, cellRanges);
return this._proxy.$executeNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellRanges);
},
cancelNotebookCellExecution: (uri: URI, cellRanges: ICellRange[]): Promise<void> => {
this._logService.debug('MainthreadNotebooks.cancelNotebookCellExecution', uri.path, dto.friendlyId, cellRanges);
return this._proxy.$cancelNotebookCellExecution(handle, uri, dto.friendlyId, cellRanges);
const provider: INotebookCellStatusBarItemProvider = {
async provideCellStatusBarItems(uri: URI, index: number, token: CancellationToken) {
const result = await that._proxy.$provideNotebookCellStatusBarItems(handle, uri, index, token);
return {
items: result?.items ?? [],
dispose() {
if (result) {
that._proxy.$releaseNotebookCellStatusBarItems(result.cacheId);
}
});
}
return result;
}
});
this._notebookKernelProviders.set(handle, { extension, emitter, provider });
return;
}
async $unregisterNotebookKernelProvider(handle: number): Promise<void> {
const entry = this._notebookKernelProviders.get(handle);
if (entry) {
entry.emitter.dispose();
entry.provider.dispose();
this._notebookKernelProviders.delete(handle);
}
}
$onNotebookKernelChange(handle: number, uriComponents: UriComponents): void {
const entry = this._notebookKernelProviders.get(handle);
entry?.emitter.fire(uriComponents ? URI.revive(uriComponents) : undefined);
}
async $postMessage(id: string, forRendererId: string | undefined, value: any): Promise<boolean> {
const editor = this._notebookEditorService.getNotebookEditor(id);
if (!editor) {
return false;
}
editor.postMessage(forRendererId, value);
return true;
}
async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise<void> {
const editor = this._notebookEditorService.getNotebookEditor(id);
if (!editor) {
return;
}
const notebookEditor = editor as INotebookEditor;
if (!notebookEditor.hasModel()) {
return;
}
const viewModel = notebookEditor.viewModel;
const cell = viewModel.viewCells[range.start];
if (!cell) {
return;
}
switch (revealType) {
case NotebookEditorRevealType.Default:
return notebookEditor.revealCellRangeInView(range);
case NotebookEditorRevealType.InCenter:
return notebookEditor.revealInCenter(cell);
case NotebookEditorRevealType.InCenterIfOutsideViewport:
return notebookEditor.revealInCenterIfOutsideViewport(cell);
case NotebookEditorRevealType.AtTop:
return notebookEditor.revealInViewAtTop(cell);
}
}
$registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void {
this._notebookEditorService.registerEditorDecorationType(key, options);
}
$removeNotebookEditorDecorationType(key: string): void {
this._notebookEditorService.removeEditorDecorationType(key);
}
$trySetDecorations(id: string, range: ICellRange, key: string): void {
const editor = this._notebookEditorService.getNotebookEditor(id);
if (editor) {
const notebookEditor = editor as INotebookEditor;
notebookEditor.setEditorDecorations(key, range);
}
}
async $setStatusBarEntry(id: number, rawStatusBarEntry: INotebookCellStatusBarEntryDto): Promise<void> {
const statusBarEntry = {
...rawStatusBarEntry,
...{ cellResource: URI.revive(rawStatusBarEntry.cellResource) }
}
};
},
selector: selector
};
const existingEntry = this._cellStatusBarEntries.get(id);
if (existingEntry) {
existingEntry.dispose();
if (typeof eventHandle === 'number') {
const emitter = new Emitter<void>();
this._notebookCellStatusBarRegistrations.set(eventHandle, emitter);
provider.onDidChangeStatusBarItems = emitter.event;
}
if (statusBarEntry.visible) {
this._cellStatusBarEntries.set(id, this._cellStatusBarService.addEntry(statusBarEntry));
}
const disposable = this._cellStatusBarService.registerCellStatusBarItemProvider(provider);
this._notebookCellStatusBarRegistrations.set(handle, disposable);
}
async $tryOpenDocument(uriComponents: UriComponents): Promise<URI> {
const uri = URI.revive(uriComponents);
const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined);
this._modelReferenceCollection.add(uri, ref);
return uri;
}
async $trySaveDocument(uriComponents: UriComponents) {
const uri = URI.revive(uriComponents);
const ref = await this._notebookEditorModelResolverService.resolve(uri);
const saveResult = await ref.object.save();
ref.dispose();
return saveResult;
}
async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise<string> {
const editorOptions = new NotebookEditorOptions({
cellSelections: options.selection && [options.selection],
preserveFocus: options.preserveFocus,
pinned: options.pinned,
// selection: options.selection,
// preserve pre 1.38 behaviour to not make group active when preserveFocus: true
// but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633
activation: options.preserveFocus ? EditorActivation.RESTORE : undefined,
override: EditorOverride.DISABLED,
});
const input = NotebookEditorInput.create(this._instantiationService, URI.revive(resource), viewType);
const editorPane = await this._editorService.openEditor(input, editorOptions, options.position);
const notebookEditor = getNotebookEditorFromEditorPane(editorPane);
if (notebookEditor) {
return notebookEditor.getId();
} else {
throw new Error(`Notebook Editor creation failure for documenet ${resource}`);
async $unregisterNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined): Promise<void> {
const unregisterThing = (handle: number) => {
const entry = this._notebookCellStatusBarRegistrations.get(handle);
if (entry) {
this._notebookCellStatusBarRegistrations.get(handle)?.dispose();
this._notebookCellStatusBarRegistrations.delete(handle);
}
};
unregisterThing(handle);
if (typeof eventHandle === 'number') {
unregisterThing(eventHandle);
}
}
}

View File

@@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { URI, UriComponents } from 'vs/base/common/uri';
import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { IImmediateCellEditOperation, IMainCellDto, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainThreadNotebookDocumentsShape } from '../common/extHost.protocol';
import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors';
import { onUnexpectedError } from 'vs/base/common/errors';
export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape {
private readonly _disposables = new DisposableStore();
private readonly _proxy: ExtHostNotebookShape;
private readonly _documentEventListenersMapping = new ResourceMap<DisposableStore>();
private readonly _modelReferenceCollection: BoundModelReferenceCollection;
constructor(
extHostContext: IExtHostContext,
notebooksAndEditors: MainThreadNotebooksAndEditors,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService,
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri);
notebooksAndEditors.onDidAddNotebooks(this._handleNotebooksAdded, this, this._disposables);
notebooksAndEditors.onDidRemoveNotebooks(this._handleNotebooksRemoved, this, this._disposables);
// forward dirty and save events
this._disposables.add(this._notebookEditorModelResolverService.onDidChangeDirty(model => this._proxy.$acceptDirtyStateChanged(model.resource, model.isDirty())));
this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => this._proxy.$acceptModelSaved(e)));
}
dispose(): void {
this._disposables.dispose();
this._modelReferenceCollection.dispose();
dispose(this._documentEventListenersMapping.values());
}
private _handleNotebooksAdded(notebooks: readonly NotebookTextModel[]): void {
for (const textModel of notebooks) {
const disposableStore = new DisposableStore();
disposableStore.add(textModel.onDidChangeContent(event => {
const dto = event.rawEvents.map(e => {
const data =
e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize
? {
kind: e.kind,
versionId: event.versionId,
changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => MainThreadNotebookDocuments._cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]])
}
: (
e.kind === NotebookCellsChangeType.Move
? {
kind: e.kind,
index: e.index,
length: e.length,
newIdx: e.newIdx,
versionId: event.versionId,
cells: e.cells.map(cell => MainThreadNotebookDocuments._cellToDto(cell as NotebookCellTextModel))
}
: e
);
return data;
});
// using the model resolver service to know if the model is dirty or not.
// assuming this is the first listener it can mean that at first the model
// is marked as dirty and that another event is fired
this._proxy.$acceptModelChanged(
textModel.uri,
{ rawEvents: dto, versionId: event.versionId },
this._notebookEditorModelResolverService.isDirty(textModel.uri)
);
const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata);
if (hasDocumentMetadataChangeEvent) {
this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata });
}
}));
this._documentEventListenersMapping.set(textModel.uri, disposableStore);
}
}
private _handleNotebooksRemoved(uris: URI[]): void {
for (const uri of uris) {
this._documentEventListenersMapping.get(uri)?.dispose();
this._documentEventListenersMapping.delete(uri);
}
}
private static _cellToDto(cell: NotebookCellTextModel): IMainCellDto {
return {
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
eol: cell.textBuffer.getEOL(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
metadata: cell.metadata
};
}
async $tryOpenDocument(uriComponents: UriComponents): Promise<URI> {
const uri = URI.revive(uriComponents);
const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined);
this._modelReferenceCollection.add(uri, ref);
return uri;
}
async $trySaveDocument(uriComponents: UriComponents) {
const uri = URI.revive(uriComponents);
const ref = await this._notebookEditorModelResolverService.resolve(uri);
const saveResult = await ref.object.save();
ref.dispose();
return saveResult;
}
async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise<void> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (!textModel) {
throw new Error(`Can't apply edits to unknown notebook model: ${URI.revive(resource).toString()}`);
}
try {
textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo);
} catch (e) {
// Clearing outputs at the same time as the EH calling append/replaceOutputItems is an expected race, and it should be a no-op.
// And any other failure should not throw back to the extension.
onUnexpectedError(e);
}
}
}

View File

@@ -0,0 +1,254 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { diffMaps, diffSets } from 'vs/base/common/collections';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MainThreadNotebookDocuments } from 'vs/workbench/api/browser/mainThreadNotebookDocuments';
import { MainThreadNotebookEditors } from 'vs/workbench/api/browser/mainThreadNotebookEditors';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { editorGroupToViewColumn } from 'vs/workbench/common/editor';
import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookModelAddedData, MainContext } from '../common/extHost.protocol';
interface INotebookAndEditorDelta {
removedDocuments: URI[];
addedDocuments: NotebookTextModel[];
removedEditors: string[];
addedEditors: IActiveNotebookEditor[];
newActiveEditor?: string | null;
visibleEditors?: string[];
}
class NotebookAndEditorState {
static compute(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): INotebookAndEditorDelta {
if (!before) {
return {
addedDocuments: [...after.documents],
removedDocuments: [],
addedEditors: [...after.textEditors.values()],
removedEditors: [],
visibleEditors: [...after.visibleEditors].map(editor => editor[0])
};
}
const documentDelta = diffSets(before.documents, after.documents);
const editorDelta = diffMaps(before.textEditors, after.textEditors);
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors);
return {
addedDocuments: documentDelta.added,
removedDocuments: documentDelta.removed.map(e => e.uri),
addedEditors: editorDelta.added,
removedEditors: editorDelta.removed.map(removed => removed.getId()),
newActiveEditor: newActiveEditor,
visibleEditors: visibleEditorDelta.added.length === 0 && visibleEditorDelta.removed.length === 0
? undefined
: [...after.visibleEditors].map(editor => editor[0])
};
}
constructor(
readonly documents: Set<NotebookTextModel>,
readonly textEditors: Map<string, IActiveNotebookEditor>,
readonly activeEditor: string | null | undefined,
readonly visibleEditors: Map<string, IActiveNotebookEditor>
) {
//
}
}
@extHostCustomer
export class MainThreadNotebooksAndEditors {
private readonly _onDidAddNotebooks = new Emitter<NotebookTextModel[]>();
private readonly _onDidRemoveNotebooks = new Emitter<URI[]>();
private readonly _onDidAddEditors = new Emitter<IActiveNotebookEditor[]>();
private readonly _onDidRemoveEditors = new Emitter<string[]>();
readonly onDidAddNotebooks: Event<NotebookTextModel[]> = this._onDidAddNotebooks.event;
readonly onDidRemoveNotebooks: Event<URI[]> = this._onDidRemoveNotebooks.event;
readonly onDidAddEditors: Event<IActiveNotebookEditor[]> = this._onDidAddEditors.event;
readonly onDidRemoveEditors: Event<string[]> = this._onDidRemoveEditors.event;
private readonly _proxy: Pick<ExtHostNotebookShape, '$acceptDocumentAndEditorsDelta'>;
private readonly _disposables = new DisposableStore();
private readonly _editorListeners = new Map<string, IDisposable>();
private _currentState?: NotebookAndEditorState;
private readonly _mainThreadNotebooks: MainThreadNotebookDocuments;
private readonly _mainThreadEditors: MainThreadNotebookEditors;
constructor(
extHostContext: IExtHostContext,
@IInstantiationService instantiationService: IInstantiationService,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorService private readonly _notebookEditorService: INotebookEditorService,
@IEditorService private readonly _editorService: IEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
this._mainThreadNotebooks = instantiationService.createInstance(MainThreadNotebookDocuments, extHostContext, this);
this._mainThreadEditors = instantiationService.createInstance(MainThreadNotebookEditors, extHostContext, this);
extHostContext.set(MainContext.MainThreadNotebookDocuments, this._mainThreadNotebooks);
extHostContext.set(MainContext.MainThreadNotebookEditors, this._mainThreadEditors);
this._notebookService.onDidCreateNotebookDocument(() => this._updateState(), this, this._disposables);
this._notebookService.onDidRemoveNotebookDocument(() => this._updateState(), this, this._disposables);
this._editorService.onDidActiveEditorChange(() => this._updateState(), this, this._disposables);
this._editorService.onDidVisibleEditorsChange(() => this._updateState(), this, this._disposables);
this._notebookEditorService.onDidAddNotebookEditor(this._handleEditorAdd, this, this._disposables);
this._notebookEditorService.onDidRemoveNotebookEditor(this._handleEditorRemove, this, this._disposables);
this._updateState();
}
dispose() {
this._mainThreadNotebooks.dispose();
this._mainThreadEditors.dispose();
this._onDidAddEditors.dispose();
this._onDidRemoveEditors.dispose();
this._onDidAddNotebooks.dispose();
this._onDidRemoveNotebooks.dispose();
this._disposables.dispose();
}
private _handleEditorAdd(editor: INotebookEditor): void {
this._editorListeners.set(editor.getId(), combinedDisposable(
editor.onDidChangeModel(() => this._updateState()),
editor.onDidFocusEditorWidget(() => this._updateState(editor)),
));
this._updateState();
}
private _handleEditorRemove(editor: INotebookEditor): void {
this._editorListeners.get(editor.getId())?.dispose();
this._editorListeners.delete(editor.getId());
this._updateState();
}
private _updateState(focusedEditor?: INotebookEditor): void {
const editors = new Map<string, IActiveNotebookEditor>();
const visibleEditorsMap = new Map<string, IActiveNotebookEditor>();
for (const editor of this._notebookEditorService.listNotebookEditors()) {
if (editor.hasModel()) {
editors.set(editor.getId(), editor);
}
}
const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
let activeEditor: string | null = null;
if (activeNotebookEditor) {
activeEditor = activeNotebookEditor.getId();
} else if (focusedEditor?.textModel) {
activeEditor = focusedEditor.getId();
}
if (activeEditor && !editors.has(activeEditor)) {
activeEditor = null;
}
for (const editorPane of this._editorService.visibleEditorPanes) {
const notebookEditor = getNotebookEditorFromEditorPane(editorPane);
if (notebookEditor?.hasModel() && editors.has(notebookEditor.getId())) {
visibleEditorsMap.set(notebookEditor.getId(), notebookEditor);
}
}
const newState = new NotebookAndEditorState(new Set(this._notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap);
this._onDelta(NotebookAndEditorState.compute(this._currentState, newState));
this._currentState = newState;
}
private _onDelta(delta: INotebookAndEditorDelta): void {
if (MainThreadNotebooksAndEditors._isDeltaEmpty(delta)) {
return;
}
const dto: INotebookDocumentsAndEditorsDelta = {
removedDocuments: delta.removedDocuments,
removedEditors: delta.removedEditors,
newActiveEditor: delta.newActiveEditor,
visibleEditors: delta.visibleEditors,
addedDocuments: delta.addedDocuments.map(MainThreadNotebooksAndEditors._asModelAddData),
addedEditors: delta.addedEditors.map(this._asEditorAddData, this),
};
// send to extension FIRST
this._proxy.$acceptDocumentAndEditorsDelta(dto);
// handle internally
this._onDidRemoveEditors.fire(delta.removedEditors);
this._onDidRemoveNotebooks.fire(delta.removedDocuments);
this._onDidAddNotebooks.fire(delta.addedDocuments);
this._onDidAddEditors.fire(delta.addedEditors);
}
private static _isDeltaEmpty(delta: INotebookAndEditorDelta): boolean {
if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) {
return false;
}
if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) {
return false;
}
if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) {
return false;
}
if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) {
return false;
}
if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) {
return false;
}
if (delta.newActiveEditor !== undefined) {
return false;
}
return true;
}
private static _asModelAddData(e: NotebookTextModel): INotebookModelAddedData {
return {
viewType: e.viewType,
uri: e.uri,
metadata: e.metadata,
versionId: e.versionId,
cells: e.cells.map(cell => ({
handle: cell.handle,
uri: cell.uri,
source: cell.textBuffer.getLinesContent(),
eol: cell.textBuffer.getEOL(),
language: cell.language,
cellKind: cell.cellKind,
outputs: cell.outputs,
metadata: cell.metadata
}))
};
}
private _asEditorAddData(add: IActiveNotebookEditor): INotebookEditorAddData {
const pane = this._editorService.visibleEditorPanes.find(pane => getNotebookEditorFromEditorPane(pane) === add);
return {
id: add.getId(),
documentUri: add.viewModel.uri,
selections: add.getSelections(),
visibleRanges: add.visibleRanges,
viewColumn: pane && editorGroupToViewColumn(this._editorGroupService, pane.group)
};
}
}

View File

@@ -0,0 +1,191 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { getNotebookEditorFromEditorPane, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookDocumentShowOptions, INotebookEditorViewColumnInfo, MainThreadNotebookEditorsShape, NotebookEditorRevealType } from '../common/extHost.protocol';
import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors';
import { ICellEditOperation, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { ILogService } from 'vs/platform/log/common/log';
import { URI, UriComponents } from 'vs/base/common/uri';
import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { editorGroupToViewColumn } from 'vs/workbench/common/editor';
import { equals } from 'vs/base/common/objects';
class MainThreadNotebook {
constructor(
readonly editor: INotebookEditor,
readonly disposables: DisposableStore
) { }
dispose() {
this.disposables.dispose();
}
}
export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape {
private readonly _disposables = new DisposableStore();
private readonly _proxy: ExtHostNotebookShape;
private readonly _mainThreadEditors = new Map<string, MainThreadNotebook>();
private _currentViewColumnInfo?: INotebookEditorViewColumnInfo;
constructor(
extHostContext: IExtHostContext,
notebooksAndEditors: MainThreadNotebooksAndEditors,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEditorService private readonly _editorService: IEditorService,
@ILogService private readonly _logService: ILogService,
@INotebookEditorService private readonly _notebookEditorService: INotebookEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
notebooksAndEditors.onDidAddEditors(this._handleEditorsAdded, this, this._disposables);
notebooksAndEditors.onDidRemoveEditors(this._handleEditorsRemoved, this, this._disposables);
this._editorService.onDidActiveEditorChange(() => this._updateEditorViewColumns(), this, this._disposables);
this._editorGroupService.onDidRemoveGroup(() => this._updateEditorViewColumns(), this, this._disposables);
this._editorGroupService.onDidMoveGroup(() => this._updateEditorViewColumns(), this, this._disposables);
}
dispose(): void {
this._disposables.dispose();
dispose(this._mainThreadEditors.values());
}
private _handleEditorsAdded(editors: readonly INotebookEditor[]): void {
for (const editor of editors) {
const editorDisposables = new DisposableStore();
editorDisposables.add(editor.onDidChangeVisibleRanges(() => {
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } });
}));
editorDisposables.add(editor.onDidChangeSelection(() => {
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } });
}));
const wrapper = new MainThreadNotebook(editor, editorDisposables);
this._mainThreadEditors.set(editor.getId(), wrapper);
}
}
private _handleEditorsRemoved(editorIds: readonly string[]): void {
for (const id of editorIds) {
this._mainThreadEditors.get(id)?.dispose();
this._mainThreadEditors.delete(id);
}
}
private _updateEditorViewColumns(): void {
const result: INotebookEditorViewColumnInfo = Object.create(null);
for (let editorPane of this._editorService.visibleEditorPanes) {
const candidate = getNotebookEditorFromEditorPane(editorPane);
if (candidate && this._mainThreadEditors.has(candidate.getId())) {
result[candidate.getId()] = editorGroupToViewColumn(this._editorGroupService, editorPane.group);
}
}
if (!equals(result, this._currentViewColumnInfo)) {
this._currentViewColumnInfo = result;
this._proxy.$acceptEditorViewColumns(result);
}
}
async $tryApplyEdits(editorId: string, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise<boolean> {
const wrapper = this._mainThreadEditors.get(editorId);
if (!wrapper) {
return false;
}
const { editor } = wrapper;
if (!editor.textModel) {
this._logService.warn('Notebook editor has NO model', editorId);
return false;
}
if (editor.textModel.versionId !== modelVersionId) {
return false;
}
//todo@jrieken use proper selection logic!
return editor.textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined);
}
async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise<string> {
const editorOptions = new NotebookEditorOptions({
cellSelections: options.selections,
preserveFocus: options.preserveFocus,
pinned: options.pinned,
// selection: options.selection,
// preserve pre 1.38 behaviour to not make group active when preserveFocus: true
// but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633
activation: options.preserveFocus ? EditorActivation.RESTORE : undefined,
override: EditorOverride.DISABLED,
});
const input = NotebookEditorInput.create(this._instantiationService, URI.revive(resource), viewType);
const editorPane = await this._editorService.openEditor(input, editorOptions, options.position);
const notebookEditor = getNotebookEditorFromEditorPane(editorPane);
if (notebookEditor) {
return notebookEditor.getId();
} else {
throw new Error(`Notebook Editor creation failure for documenet ${resource}`);
}
}
async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise<void> {
const editor = this._notebookEditorService.getNotebookEditor(id);
if (!editor) {
return;
}
const notebookEditor = editor as INotebookEditor;
if (!notebookEditor.hasModel()) {
return;
}
const viewModel = notebookEditor.viewModel;
const cell = viewModel.cellAt(range.start);
if (!cell) {
return;
}
switch (revealType) {
case NotebookEditorRevealType.Default:
return notebookEditor.revealCellRangeInView(range);
case NotebookEditorRevealType.InCenter:
return notebookEditor.revealInCenter(cell);
case NotebookEditorRevealType.InCenterIfOutsideViewport:
return notebookEditor.revealInCenterIfOutsideViewport(cell);
case NotebookEditorRevealType.AtTop:
return notebookEditor.revealInViewAtTop(cell);
}
}
$registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void {
this._notebookEditorService.registerEditorDecorationType(key, options);
}
$removeNotebookEditorDecorationType(key: string): void {
this._notebookEditorService.removeEditorDecorationType(key);
}
$trySetDecorations(id: string, range: ICellRange, key: string): void {
const editor = this._notebookEditorService.getNotebookEditor(id);
if (editor) {
const notebookEditor = editor as INotebookEditor;
notebookEditor.setEditorDecorations(key, range);
}
}
}

View File

@@ -0,0 +1,225 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { flatten, isNonEmptyArray } from 'vs/base/common/arrays';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { INotebookKernel, INotebookKernelChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { ExtHostContext, ExtHostNotebookKernelsShape, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol';
abstract class MainThreadKernel implements INotebookKernel {
private readonly _onDidChange = new Emitter<INotebookKernelChangeEvent>();
private readonly preloads: { uri: URI, provides: string[] }[];
readonly onDidChange: Event<INotebookKernelChangeEvent> = this._onDidChange.event;
readonly id: string;
readonly viewType: string;
readonly extension: ExtensionIdentifier;
implementsInterrupt: boolean;
label: string;
description?: string;
detail?: string;
supportedLanguages: string[];
implementsExecutionOrder: boolean;
localResourceRoot: URI;
public get preloadUris() {
return this.preloads.map(p => p.uri);
}
public get preloadProvides() {
return flatten(this.preloads.map(p => p.provides));
}
constructor(data: INotebookKernelDto2, private _modeService: IModeService) {
this.id = data.id;
this.viewType = data.viewType;
this.extension = data.extensionId;
this.implementsInterrupt = data.supportsInterrupt ?? false;
this.label = data.label;
this.description = data.description;
this.detail = data.detail;
this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _modeService.getRegisteredModes();
this.implementsExecutionOrder = data.hasExecutionOrder ?? false;
this.localResourceRoot = URI.revive(data.extensionLocation);
this.preloads = data.preloads?.map(u => ({ uri: URI.revive(u.uri), provides: u.provides })) ?? [];
}
update(data: Partial<INotebookKernelDto2>) {
const event: INotebookKernelChangeEvent = Object.create(null);
if (data.label !== undefined) {
this.label = data.label;
event.label = true;
}
if (data.description !== undefined) {
this.description = data.description;
event.description = true;
}
if (data.detail !== undefined) {
this.detail = data.detail;
event.detail = true;
}
if (data.supportedLanguages !== undefined) {
this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._modeService.getRegisteredModes();
event.supportedLanguages = true;
}
if (data.hasExecutionOrder !== undefined) {
this.implementsExecutionOrder = data.hasExecutionOrder;
event.hasExecutionOrder = true;
}
this._onDidChange.fire(event);
}
abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise<void>;
abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void>;
}
@extHostNamedCustomer(MainContext.MainThreadNotebookKernels)
export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape {
private readonly _editors = new Map<INotebookEditor, IDisposable>();
private readonly _disposables = new DisposableStore();
private readonly _kernels = new Map<number, [kernel: MainThreadKernel, registraion: IDisposable]>();
private readonly _proxy: ExtHostNotebookKernelsShape;
constructor(
extHostContext: IExtHostContext,
@IModeService private readonly _modeService: IModeService,
@INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
@INotebookEditorService notebookEditorService: INotebookEditorService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookKernels);
notebookEditorService.listNotebookEditors().forEach(this._onEditorAdd, this);
notebookEditorService.onDidAddNotebookEditor(this._onEditorAdd, this, this._disposables);
notebookEditorService.onDidRemoveNotebookEditor(this._onEditorRemove, this, this._disposables);
}
dispose(): void {
this._disposables.dispose();
for (let [, registration] of this._kernels.values()) {
registration.dispose();
}
}
// --- kernel ipc
private _onEditorAdd(editor: INotebookEditor) {
const ipcListener = editor.onDidReceiveMessage(e => {
if (e.forRenderer) {
return;
}
if (!editor.hasModel()) {
return;
}
const { selected } = this._notebookKernelService.getMatchingKernel(editor.viewModel.notebookDocument);
if (!selected) {
return;
}
for (let [handle, candidate] of this._kernels) {
if (candidate[0] === selected) {
this._proxy.$acceptRendererMessage(handle, editor.getId(), e.message);
break;
}
}
});
this._editors.set(editor, ipcListener);
}
private _onEditorRemove(editor: INotebookEditor) {
this._editors.get(editor)?.dispose();
this._editors.delete(editor);
}
async $postMessage(handle: number, editorId: string | undefined, message: any): Promise<boolean> {
const tuple = this._kernels.get(handle);
if (!tuple) {
throw new Error('kernel already disposed');
}
const [kernel] = tuple;
let didSend = false;
for (const [editor] of this._editors) {
if (!editor.hasModel()) {
continue;
}
if (this._notebookKernelService.getMatchingKernel(editor.viewModel.notebookDocument).selected !== kernel) {
// different kernel
continue;
}
if (editorId === undefined) {
// all editors
editor.postMessage(undefined, message);
didSend = true;
} else if (editor.getId() === editorId) {
// selected editors
editor.postMessage(undefined, message);
didSend = true;
break;
}
}
return didSend;
}
// --- kernel adding/updating/removal
async $addKernel(handle: number, data: INotebookKernelDto2): Promise<void> {
const that = this;
const kernel = new class extends MainThreadKernel {
async executeNotebookCellsRequest(uri: URI, handles: number[]): Promise<void> {
await that._proxy.$executeCells(handle, uri, handles);
}
async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise<void> {
await that._proxy.$cancelCells(handle, uri, handles);
}
}(data, this._modeService);
const registration = this._notebookKernelService.registerKernel(kernel);
const listener = this._notebookKernelService.onDidChangeNotebookKernelBinding(e => {
if (e.oldKernel === kernel.id) {
this._proxy.$acceptSelection(handle, e.notebook, false);
} else if (e.newKernel === kernel.id) {
this._proxy.$acceptSelection(handle, e.notebook, true);
}
});
this._kernels.set(handle, [kernel, combinedDisposable(listener, registration)]);
}
$updateKernel(handle: number, data: Partial<INotebookKernelDto2>): void {
const tuple = this._kernels.get(handle);
if (tuple) {
tuple[0].update(data);
}
}
$removeKernel(handle: number): void {
const tuple = this._kernels.get(handle);
if (tuple) {
tuple[1].dispose();
this._kernels.delete(handle);
}
}
$updateNotebookPriority(handle: number, notebook: UriComponents, value: number | undefined): void {
const tuple = this._kernels.get(handle);
if (tuple) {
this._notebookKernelService.updateKernelNotebookAffinity(tuple[0], URI.revive(notebook), value);
}
}
}

View File

@@ -6,7 +6,7 @@
import { URI, UriComponents } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm';
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { Command } from 'vs/editor/common/modes';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@@ -424,6 +424,24 @@ export class MainThreadSCM implements MainThreadSCMShape {
repository.input.visible = visible;
}
$setInputBoxFocus(sourceControlHandle: number): void {
const repository = this._repositories.get(sourceControlHandle);
if (!repository) {
return;
}
repository.input.setFocus();
}
$showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType) {
const repository = this._repositories.get(sourceControlHandle);
if (!repository) {
return;
}
repository.input.showValidationMessage(message, type);
}
$setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void {
const repository = this._repositories.get(sourceControlHandle);

View File

@@ -138,7 +138,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
return Promise.resolve(searchP).then((result: ISearchCompleteStats) => {
this._searches.delete(search.id);
return { results: Array.from(search.matches.values()), stats: result.stats, limitHit: result.limitHit };
return { results: Array.from(search.matches.values()), stats: result.stats, limitHit: result.limitHit, messages: result.messages };
}, err => {
this._searches.delete(search.id);
return Promise.reject(err);

View File

@@ -10,12 +10,12 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { dispose } from 'vs/base/common/lifecycle';
import { Command } from 'vs/editor/common/modes';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { getCodiconAriaLabel } from 'vs/base/common/codicons';
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
export class MainThreadStatusBar implements MainThreadStatusBarShape {
private readonly entries: Map<number, { accessor: IStatusbarEntryAccessor, alignment: MainThreadStatusBarAlignment, priority: number }> = new Map();
private static readonly CODICON_REGEXP = /\$\((.*?)\)/g;
constructor(
_extHostContext: IExtHostContext,
@@ -35,7 +35,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
ariaLabel = accessibilityInformation.label;
role = accessibilityInformation.role;
} else {
ariaLabel = text ? text.replace(MainThreadStatusBar.CODICON_REGEXP, (_match, codiconName) => codiconName) : '';
ariaLabel = getCodiconAriaLabel(text);
}
const entry: IStatusbarEntry = { text, tooltip, command, color, backgroundColor, ariaLabel, role };

View File

@@ -422,7 +422,7 @@ export class MainThreadTask implements MainThreadTaskShape {
if (execution.task?.execution && CustomExecutionDTO.is(execution.task.execution) && event.resolvedVariables) {
const dictionary: IStringDictionary<string> = {};
Array.from(event.resolvedVariables.entries()).forEach(entry => dictionary[entry[0]] = entry[1]);
resolvedDefinition = await this._configurationResolverService.resolveAny(task.getWorkspaceFolder(),
resolvedDefinition = await this._configurationResolverService.resolveAnyAsync(task.getWorkspaceFolder(),
execution.task.definition, dictionary);
}
this._proxy.$onDidStartTask(execution, event.terminalId!, resolvedDefinition);

View File

@@ -8,11 +8,12 @@ import { StopWatch } from 'vs/base/common/stopwatch';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IShellLaunchConfig, ITerminalDimensions } from 'vs/platform/terminal/common/terminal';
import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions } from 'vs/platform/terminal/common/terminal';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
import { ExtHostContext, ExtHostTerminalServiceShape, IExtHostContext, IShellLaunchConfigDto, ITerminalDimensionsDto, MainContext, MainThreadTerminalServiceShape, TerminalIdentifier, TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostContext, ExtHostTerminalServiceShape, IExtHostContext, ITerminalDimensionsDto, MainContext, MainThreadTerminalServiceShape, TerminalIdentifier, TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ITerminalExternalLinkProvider, ITerminalInstance, ITerminalInstanceService, ITerminalLink, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
import { IAvailableProfilesRequest as IAvailableProfilesRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -69,7 +70,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._toDispose.add(_terminalService.onInstanceRequestStartExtensionTerminal(e => this._onRequestStartExtensionTerminal(e)));
this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null)));
this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => instance && this._onTitleChanged(instance.instanceId, instance.title)));
this._toDispose.add(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed)));
this._toDispose.add(_terminalService.onRequestAvailableProfiles(e => this._onRequestAvailableProfiles(e)));
// ITerminalInstanceService listeners
@@ -100,9 +100,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
public dispose(): void {
this._toDispose.dispose();
this._linkProvider?.dispose();
// TODO@Daniel: Should all the previously created terminals be disposed
// when the extension host process goes down ?
}
private _getTerminalId(id: TerminalIdentifier): number | undefined {
@@ -126,12 +123,16 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
executable: launchConfig.shellPath,
args: launchConfig.shellArgs,
cwd: typeof launchConfig.cwd === 'string' ? launchConfig.cwd : URI.revive(launchConfig.cwd),
icon: launchConfig.icon,
initialText: launchConfig.initialText,
waitOnExit: launchConfig.waitOnExit,
ignoreConfigurationCwd: true,
env: launchConfig.env,
strictEnv: launchConfig.strictEnv,
hideFromUser: launchConfig.hideFromUser,
isExtensionCustomPtyTerminal: launchConfig.isExtensionCustomPtyTerminal,
customPtyImplementation: launchConfig.isExtensionCustomPtyTerminal
? (id, cols, rows) => new TerminalProcessExtHostProxy(id, cols, rows, this._terminalService)
: undefined,
extHostTerminalId: extHostTerminalId,
isFeatureTerminal: launchConfig.isFeatureTerminal,
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal
@@ -157,17 +158,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $dispose(id: TerminalIdentifier): void {
const terminalInstance = this._getTerminalInstance(id);
if (terminalInstance) {
terminalInstance.dispose();
}
this._getTerminalInstance(id)?.dispose();
}
public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void {
const terminalInstance = this._getTerminalInstance(id);
if (terminalInstance) {
terminalInstance.sendText(text, addNewLine);
}
this._getTerminalInstance(id)?.sendText(text, addNewLine);
}
public $startSendingDataEvents(): void {
@@ -183,10 +178,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $stopSendingDataEvents(): void {
if (this._dataEventTracker) {
this._dataEventTracker.dispose();
this._dataEventTracker = undefined;
}
this._dataEventTracker?.dispose();
this._dataEventTracker = undefined;
}
public $startLinkProvider(): void {
@@ -215,10 +208,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$acceptTerminalTitleChange(terminalId, name);
}
private _onWorkspacePermissionsChanged(isAllowed: boolean): void {
this._proxy.$acceptWorkspacePermissionsChanged(isAllowed);
}
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
this._proxy.$acceptTerminalClosed(terminalInstance.instanceId, terminalInstance.exitCode);
}
@@ -276,60 +265,35 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $sendProcessTitle(terminalId: number, title: string): void {
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitTitle(title);
}
this._terminalProcessProxies.get(terminalId)?.emitTitle(title);
}
public $sendProcessData(terminalId: number, data: string): void {
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitData(data);
}
this._terminalProcessProxies.get(terminalId)?.emitData(data);
}
public $sendProcessReady(terminalId: number, pid: number, cwd: string): void {
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitReady(pid, cwd);
}
this._terminalProcessProxies.get(terminalId)?.emitReady(pid, cwd);
}
public $sendProcessExit(terminalId: number, exitCode: number | undefined): void {
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitExit(exitCode);
this._terminalProcessProxies.delete(terminalId);
}
this._terminalProcessProxies.get(terminalId)?.emitExit(exitCode);
}
public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void {
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitOverrideDimensions(dimensions);
}
this._terminalProcessProxies.get(terminalId)?.emitOverrideDimensions(dimensions);
}
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitInitialCwd(initialCwd);
}
this._terminalProcessProxies.get(terminalId)?.emitInitialCwd(initialCwd);
}
public $sendProcessCwd(terminalId: number, cwd: string): void {
const terminalProcess = this._terminalProcessProxies.get(terminalId);
if (terminalProcess) {
terminalProcess.emitCwd(cwd);
}
this._terminalProcessProxies.get(terminalId)?.emitCwd(cwd);
}
public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void {
const instance = this._terminalService.getInstanceFromId(terminalId);
if (instance) {
this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig);
}
this._getTerminalProcess(terminalId)?.emitResolvedShellLaunchConfig(shellLaunchConfig);
}
private async _onRequestLatency(terminalId: number): Promise<void> {
@@ -350,12 +314,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
if (conn) {
return this._remoteAuthority === conn.remoteAuthority;
}
return true;
return this._extHostKind !== ExtensionHostKind.LocalWebWorker;
}
private async _onRequestAvailableProfiles(req: IAvailableProfilesRequest): Promise<void> {
if (this._isPrimaryExtHost() && this._extHostKind !== ExtensionHostKind.LocalWebWorker) {
req.callback(await this._proxy.$getAvailableProfiles(req.quickLaunchOnly));
if (this._isPrimaryExtHost()) {
req.callback(await this._proxy.$getAvailableProfiles(req.configuredProfilesOnly));
}
}

View File

@@ -3,14 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { isDefined } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { getTestSubscriptionKey, ISerializedTestResults, ITestState, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { HydratedTestResult, ITestResultService, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResultService';
import { TestResultState } from 'vs/workbench/api/common/extHostTypes';
import { ExtensionRunTestsRequest, getTestSubscriptionKey, ITestItem, ITestMessage, ITestRunTask, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
@@ -44,7 +47,6 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri)));
this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri)));
const prevResults = resultService.results.map(r => r.toJSON()).filter(isDefined);
if (prevResults.length) {
this.proxy.$publishTestResults(prevResults);
@@ -68,31 +70,79 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
/**
* @inheritdoc
*/
public $publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void {
this.resultService.push(new HydratedTestResult(results, persist));
$addTestsToRun(runId: string, tests: ITestItem[]): void {
for (const test of tests) {
test.uri = URI.revive(test.uri);
if (test.range) {
test.range = Range.lift(test.range);
}
}
this.withLiveRun(runId, r => r.addTestChainToRun(tests));
}
/**
* @inheritdoc
*/
public $updateTestStateInRun(runId: string, testId: string, state: ITestState): void {
$startedExtensionTestRun(req: ExtensionRunTestsRequest): void {
this.resultService.createLiveResult(req);
}
/**
* @inheritdoc
*/
$startedTestRunTask(runId: string, task: ITestRunTask): void {
this.withLiveRun(runId, r => r.addTask(task));
}
/**
* @inheritdoc
*/
$finishedTestRunTask(runId: string, taskId: string): void {
this.withLiveRun(runId, r => r.markTaskComplete(taskId));
}
/**
* @inheritdoc
*/
$finishedExtensionTestRun(runId: string): void {
this.withLiveRun(runId, r => r.markComplete());
}
/**
* @inheritdoc
*/
public $updateTestStateInRun(runId: string, taskId: string, testId: string, state: TestResultState, duration?: number): void {
this.withLiveRun(runId, r => r.updateState(testId, taskId, state, duration));
}
/**
* @inheritdoc
*/
public $appendOutputToRun(runId: string, _taskId: string, output: VSBuffer): void {
this.withLiveRun(runId, r => r.output.append(output));
}
/**
* @inheritdoc
*/
public $appendTestMessageInRun(runId: string, taskId: string, testId: string, message: ITestMessage): void {
const r = this.resultService.getResult(runId);
if (r && r instanceof LiveTestResult) {
for (const message of state.messages) {
if (message.location) {
message.location.uri = URI.revive(message.location.uri);
message.location.range = Range.lift(message.location.range);
}
if (message.location) {
message.location.uri = URI.revive(message.location.uri);
message.location.range = Range.lift(message.location.range);
}
r.updateState(testId, state);
r.appendMessage(testId, taskId, message);
}
}
/**
* @inheritdoc
*/
public $registerTestProvider(id: string) {
public $registerTestController(id: string) {
const disposable = this.testService.registerTestController(id, {
runTests: (req, token) => this.proxy.$runTestsForProvider(req, token),
lookupTest: test => this.proxy.$lookupTest(test),
@@ -105,7 +155,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
/**
* @inheritdoc
*/
public $unregisterTestProvider(id: string) {
public $unregisterTestController(id: string) {
this.testProviderRegistrations.get(id)?.dispose();
this.testProviderRegistrations.delete(id);
}
@@ -142,11 +192,16 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
return result.id;
}
public dispose() {
public override dispose() {
super.dispose();
for (const subscription of this.testSubscriptions.values()) {
subscription.dispose();
}
this.testSubscriptions.clear();
}
private withLiveRun<T>(runId: string, fn: (run: LiveTestResult) => T): T | undefined {
const r = this.resultService.getResult(runId);
return r && r instanceof LiveTestResult ? fn(r) : undefined;
}
}

View File

@@ -147,7 +147,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
return viewDescriptor ? viewDescriptor.treeView : null;
}
dispose(): void {
override dispose(): void {
this._dataProviders.forEach((dataProvider, treeViewId) => {
const treeView = this.getTreeView(treeViewId);
if (treeView) {

View File

@@ -12,10 +12,12 @@ import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderF
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider {
@@ -80,7 +82,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
const portRange = selector.portRange;
const portInRange = portRange ? ports.some(port => portRange[0] <= port && port < portRange[1]) : true;
const pidMatches = !selector.pid || (selector.pid === pid);
return portInRange || pidMatches;
const commandMatches = !selector.commandMatcher || (commandLine && (commandLine.match(selector.commandMatcher)));
return portInRange && pidMatches && commandMatches;
}).map(entry => entry[0]);
if (appropriateHandles.length === 0) {
@@ -174,19 +177,13 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
this.remoteAgentService.getEnvironment().then(() => {
switch (source) {
case CandidatePortSource.None: {
const autoDetectionEnablement = this.configurationService.inspect(PORT_AUTO_FORWARD_SETTING);
if (autoDetectionEnablement.userRemote === undefined) {
// Only update the remote setting if the user hasn't already set it.
this.configurationService.updateValue(PORT_AUTO_FORWARD_SETTING, false, ConfigurationTarget.USER_REMOTE);
}
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerDefaultConfigurations([{ 'remote.autoForwardPorts': false }]);
break;
}
case CandidatePortSource.Output: {
const candidatePortSourceSetting = this.configurationService.inspect(PORT_AUTO_SOURCE_SETTING);
if (candidatePortSourceSetting.userRemote === undefined) {
// Only update the remote setting if the user hasn't already set it.
this.configurationService.updateValue(PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, ConfigurationTarget.USER_REMOTE);
}
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerDefaultConfigurations([{ 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT }]);
break;
}
default: // Do nothing, the defaults for these settings should be used.
@@ -195,8 +192,4 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
// The remote failed to get setup. Errors from that area will already be surfaced to the user.
});
}
dispose(): void {
}
}

View File

@@ -77,8 +77,8 @@ export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpe
await this.proxy.$openUri(id, { resolvedUri: uri, sourceUri: ctx.sourceUri }, token);
} catch (e) {
if (!isPromiseCanceledError(e)) {
const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, () => {
return this.openerService.open(uri, {
const openDefaultAction = new Action('default', localize('openerFailedUseDefault', "Open using default opener"), undefined, undefined, async () => {
await this.openerService.open(uri, {
allowTunneling: false,
allowContributedOpeners: defaultExternalUriOpenerId,
});
@@ -128,7 +128,7 @@ export class MainThreadUriOpeners extends Disposable implements MainThreadUriOpe
this._contributedExternalUriOpenersStore.delete(id);
}
dispose(): void {
override dispose(): void {
super.dispose();
this._registeredOpeners.clear();
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { MainThreadWebviews, reviveWebviewContentOptions, reviveWebviewExtension } from 'vs/workbench/api/browser/mainThreadWebviews';
@@ -81,7 +81,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
private readonly _webviewInputs = new WebviewInputStore();
private readonly _editorProviders = new Map<string, IDisposable>();
private readonly _webviewFromDiffEditorHandles = new Set<string>();
private readonly _revivers = new Map<string, IDisposable>();
@@ -99,18 +98,17 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewPanels);
this._register(_editorService.onDidActiveEditorChange(() => {
const activeInput = this._editorService.activeEditor;
if (activeInput instanceof DiffEditorInput && activeInput.primary instanceof WebviewInput && activeInput.secondary instanceof WebviewInput) {
this.registerWebviewFromDiffEditorListeners(activeInput);
}
this.updateWebviewViewStates(activeInput);
this.updateWebviewViewStates(this._editorService.activeEditor);
}));
this._register(_editorService.onDidVisibleEditorsChange(() => {
this.updateWebviewViewStates(this._editorService.activeEditor);
}));
this._register(_webviewWorkbenchService.onDidChangeActiveWebviewEditor(input => {
this.updateWebviewViewStates(input);
}));
// This reviver's only job is to activate extensions.
// This should trigger the real reviver to be registered from the extension host side.
this._register(_webviewWorkbenchService.registerResolver({
@@ -125,7 +123,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
}));
}
dispose() {
override dispose() {
super.dispose();
dispose(this._editorProviders.values());
@@ -137,9 +135,9 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
public get webviewInputs(): Iterable<WebviewInput> { return this._webviewInputs; }
public addWebviewInput(handle: extHostProtocol.WebviewHandle, input: WebviewInput): void {
public addWebviewInput(handle: extHostProtocol.WebviewHandle, input: WebviewInput, options: { serializeBuffersForPostMessage: boolean }): void {
this._webviewInputs.add(handle, input);
this._mainThreadWebviews.addWebview(handle, input.webview);
this._mainThreadWebviews.addWebview(handle, input.webview, options);
input.webview.onDidDispose(() => {
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
@@ -156,6 +154,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
title: string;
webviewOptions: extHostProtocol.IWebviewOptions;
panelOptions: extHostProtocol.IWebviewPanelOptions;
serializeBuffersForPostMessage: boolean;
},
showOptions: { viewColumn?: EditorGroupColumn, preserveFocus?: boolean; },
): void {
@@ -168,7 +167,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
const extension = reviveWebviewExtension(extensionData);
const webview = this._webviewWorkbenchService.createWebview(handle, this.webviewPanelViewType.fromExternal(viewType), initData.title, mainThreadShowOptions, reviveWebviewOptions(initData.panelOptions), reviveWebviewContentOptions(initData.webviewOptions), extension);
this.addWebviewInput(handle, webview);
this.addWebviewInput(handle, webview, { serializeBuffersForPostMessage: initData.serializeBuffersForPostMessage });
/* __GDPR__
"webviews:createWebviewPanel" : {
@@ -205,7 +204,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
}
}
public $registerSerializer(viewType: string): void {
public $registerSerializer(viewType: string, options: { serializeBuffersForPostMessage: boolean }): void {
if (this._revivers.has(viewType)) {
throw new Error(`Reviver for ${viewType} already registered`);
}
@@ -223,7 +222,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
const handle = webviewInput.id;
this.addWebviewInput(handle, webviewInput);
this.addWebviewInput(handle, webviewInput, options);
let state = undefined;
if (webviewInput.webview.state) {
@@ -259,27 +258,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
this._revivers.delete(viewType);
}
private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void {
const primary = diffEditorInput.primary as WebviewInput;
const secondary = diffEditorInput.secondary as WebviewInput;
if (this._webviewFromDiffEditorHandles.has(primary.id) || this._webviewFromDiffEditorHandles.has(secondary.id)) {
return;
}
this._webviewFromDiffEditorHandles.add(primary.id);
this._webviewFromDiffEditorHandles.add(secondary.id);
const disposables = new DisposableStore();
disposables.add(primary.webview.onDidFocus(() => this.updateWebviewViewStates(primary)));
disposables.add(secondary.webview.onDidFocus(() => this.updateWebviewViewStates(secondary)));
disposables.add(diffEditorInput.onDispose(() => {
this._webviewFromDiffEditorHandles.delete(primary.id);
this._webviewFromDiffEditorHandles.delete(secondary.id);
dispose(disposables);
}));
}
private updateWebviewViewStates(activeEditorInput: IEditorInput | undefined) {
if (!this._webviewInputs.size) {
return;

View File

@@ -28,7 +28,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc
this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewViews);
}
dispose() {
override dispose() {
super.dispose();
dispose(this._webviewViewProviders.values());
@@ -55,7 +55,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc
public $registerWebviewViewProvider(
extensionData: extHostProtocol.WebviewExtensionDescription,
viewType: string,
options?: { retainContextWhenHidden?: boolean }
options: { retainContextWhenHidden?: boolean, serializeBuffersForPostMessage: boolean }
): void {
if (this._webviewViewProviders.has(viewType)) {
throw new Error(`View provider for ${viewType} already registered`);
@@ -68,7 +68,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc
const handle = webviewView.webview.id;
this._webviewViews.set(handle, webviewView);
this.mainThreadWebviews.addWebview(handle, webviewView.webview);
this.mainThreadWebviews.addWebview(handle, webviewView.webview, { serializeBuffersForPostMessage: options.serializeBuffersForPostMessage });
let state = undefined;
if (webviewView.webview.state) {

View File

@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { isWeb } from 'vs/base/common/platform';
@@ -13,6 +14,8 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { serializeMessage } from 'vs/workbench/api/common/extHostWebview';
import { deserializeWebviewMessage } from 'vs/workbench/api/common/extHostWebviewMessaging';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
export class MainThreadWebviews extends Disposable implements extHostProtocol.MainThreadWebviewsShape {
@@ -39,13 +42,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews);
}
public addWebview(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay): void {
public addWebview(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay, options: { serializeBuffersForPostMessage: boolean }): void {
if (this._webviews.has(handle)) {
throw new Error('Webview already registered');
}
this._webviews.set(handle, webview);
this.hookupWebviewEventDelegate(handle, webview);
this.hookupWebviewEventDelegate(handle, webview, options);
}
public $setHtml(handle: extHostProtocol.WebviewHandle, value: string): void {
@@ -58,17 +61,23 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
webview.contentOptions = reviveWebviewContentOptions(options);
}
public async $postMessage(handle: extHostProtocol.WebviewHandle, message: any): Promise<boolean> {
public async $postMessage(handle: extHostProtocol.WebviewHandle, jsonMessage: string, ...buffers: VSBuffer[]): Promise<boolean> {
const webview = this.getWebview(handle);
webview.postMessage(message);
const { message, arrayBuffers } = deserializeWebviewMessage(jsonMessage, buffers);
webview.postMessage(message, arrayBuffers);
return true;
}
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay) {
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay, options: { serializeBuffersForPostMessage: boolean }) {
const disposables = new DisposableStore();
disposables.add(webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri)));
disposables.add(webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); }));
disposables.add(webview.onMessage((message) => {
const serialized = serializeMessage(message.message, options);
this._proxy.$onMessage(handle, serialized.message, ...serialized.buffers);
}));
disposables.add(webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)));
disposables.add(webview.onDidDispose(() => {
@@ -80,7 +89,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void {
const webview = this.getWebview(handle);
if (this.isSupportedLink(webview, URI.parse(link))) {
this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true });
this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true, allowCommands: true });
}
}

View File

@@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ILabelService } from 'vs/platform/label/common/label';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IRequestService } from 'vs/platform/request/common/request';
import { WorkspaceTrustStateChangeEvent, IWorkspaceTrustService, WorkspaceTrustRequestOptions, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@@ -47,20 +47,21 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@ILabelService private readonly _labelService: ILabelService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IWorkspaceTrustService private readonly _workspaceTrustService: IWorkspaceTrustService
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
const workspace = this._contextService.getWorkspace();
// The workspace file is provided be a unknown file system provider. It might come
// from the extension host. So initialize now knowing that `rootPath` is undefined.
if (workspace.configuration && !isNative && !fileService.canHandleResource(workspace.configuration)) {
this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.getWorkspaceTrustState());
this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.isWorkspaceTrusted());
} else {
this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.getWorkspaceTrustState()));
this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.isWorkspaceTrusted()));
}
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose);
this._workspaceTrustService.onDidChangeTrustState(this._onDidChangeWorkspaceTrustState, this, this._toDispose);
this._workspaceTrustManagementService.onDidChangeTrust(this._onDidGrantWorkspaceTrust, this, this._toDispose);
}
dispose(): void {
@@ -208,15 +209,15 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
// --- trust ---
$requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<WorkspaceTrustState> {
return this._workspaceTrustService.requireWorkspaceTrust(options);
$requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined> {
return this._workspaceTrustRequestService.requestWorkspaceTrust(options);
}
private getWorkspaceTrustState(): WorkspaceTrustState {
return this._workspaceTrustService.getWorkspaceTrustState();
private isWorkspaceTrusted(): boolean {
return this._workspaceTrustManagementService.isWorkpaceTrusted();
}
private _onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void {
this._proxy.$onDidChangeWorkspaceTrustState(state);
private _onDidGrantWorkspaceTrust(): void {
this._proxy.$onDidGrantWorkspaceTrust();
}
}

View File

@@ -133,7 +133,7 @@ const viewDescriptor: IJSONSchema = {
type: 'string'
},
contextualTitle: {
description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used. Will be shown"),
description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used."),
type: 'string'
},
visibility: {
@@ -304,8 +304,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
const removedExtensions: Set<string> = extensionPoints.reduce((result, e) => { result.add(ExtensionIdentifier.toKey(e.description.identifier)); return result; }, new Set<string>());
for (const viewContainer of viewContainersRegistry.all) {
if (viewContainer.extensionId && removedExtensions.has(ExtensionIdentifier.toKey(viewContainer.extensionId))) {
// move only those views that do not belong to the removed extension
const views = this.viewsRegistry.getViews(viewContainer).filter(view => !removedExtensions.has(ExtensionIdentifier.toKey((view as ICustomViewDescriptor).extensionId)));
// move all views in this container into default view container
const views = this.viewsRegistry.getViews(viewContainer);
if (views.length) {
this.viewsRegistry.moveViews(views, this.getDefaultViewContainer());
}

View File

@@ -170,7 +170,7 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => {
validateProperties(configuration, extension);
configuration.id = node.id || extension.description.identifier.value;
configuration.extensionInfo = { id: extension.description.identifier.value };
configuration.extensionInfo = { id: extension.description.identifier.value, restrictedConfigurations: extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined };
configuration.title = configuration.title || extension.description.displayName || extension.description.identifier.value;
configurations.push(configuration);
return configurations;
@@ -181,10 +181,10 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => {
for (let extension of added) {
const configurations: IConfigurationNode[] = [];
const value = <IConfigurationNode | IConfigurationNode[]>extension.value;
if (!Array.isArray(value)) {
configurations.push(...handleConfiguration(value, extension));
} else {
if (Array.isArray(value)) {
value.forEach(v => configurations.push(...handleConfiguration(v, extension)));
} else {
configurations.push(...handleConfiguration(value, extension));
}
extensionConfigurations.set(ExtensionIdentifier.toKey(extension.description.identifier), configurations);
addedConfigurations.push(...configurations);
@@ -213,7 +213,7 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten
const propertyConfiguration = properties[key];
if (!isObject(propertyConfiguration)) {
delete properties[key];
extension.collector.error(nls.localize('invalid.property', "'configuration.property' must be an object"));
extension.collector.error(nls.localize('invalid.property', "configuration.properties property '{0}' must be an object", key));
continue;
}
if (propertyConfiguration.scope) {
@@ -320,7 +320,7 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', {
'remoteAuthority': {
type: 'string',
doNotSuggest: true,
description: nls.localize('workspaceConfig.remoteAuthority', "The remote server where the workspace is located. Only used by unsaved remote workspaces."),
description: nls.localize('workspaceConfig.remoteAuthority', "The remote server where the workspace is located."),
}
},
errorMessage: nls.localize('unknownWorkspaceProperty', "Unknown workspace configuration property")

View File

@@ -47,7 +47,7 @@ import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls';
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { throwProposedApiError, checkProposedApiEnabled, checkRequiresWorkspaceTrust } from 'vs/workbench/services/extensions/common/extensions';
import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import type * as vscode from 'vscode';
@@ -84,6 +84,9 @@ import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels';
import { RemoteTrustOption } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -140,7 +143,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits)));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, initData.environment, extHostLogService, extensionStoragePaths));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostLogService, extensionStoragePaths));
const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook));
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment));
@@ -333,9 +337,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
: extHostTypes.ExtensionKind.UI;
const test: typeof vscode.test = {
registerTestProvider(provider) {
registerTestController(provider) {
checkProposedApiEnabled(extension);
return extHostTesting.registerTestProvider(provider);
return extHostTesting.registerTestController(extension.identifier.value, provider);
},
createDocumentTestObserver(document) {
checkProposedApiEnabled(extension);
@@ -349,9 +353,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostTesting.runTests(provider);
},
publishTestResult(results, persist = true) {
createTestItem<T>(options: vscode.TestItemOptions, data?: T) {
return new extHostTypes.TestItemImpl(options.id, options.label, options.uri, data);
},
createTestRun(request, name, persist) {
checkProposedApiEnabled(extension);
return extHostTesting.publishExtensionProvidedResults(results, persist);
return extHostTesting.createTestRun(extension.identifier.value, request, name, persist);
},
get onDidChangeTestResults() {
checkProposedApiEnabled(extension);
@@ -363,6 +370,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
};
// todo@connor4312: backwards compatibility for a short period
(test as any).createTestRunTask = test.createTestRun;
// namespace: extensions
const extensions: typeof vscode.extensions = {
getExtension(extensionId: string): vscode.Extension<any> | undefined {
@@ -633,6 +643,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
if ('pty' in nameOrOptions) {
return extHostTerminalService.createExtensionTerminal(nameOrOptions);
}
if (nameOrOptions.message) {
checkProposedApiEnabled(extension);
}
if (nameOrOptions.icon) {
checkProposedApiEnabled(extension);
}
return extHostTerminalService.createTerminalFromOptions(nameOrOptions);
}
return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs);
@@ -900,7 +916,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables);
},
registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) => {
registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: vscode.PortAttributesProvider) => {
checkProposedApiEnabled(extension);
return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider);
},
@@ -908,18 +924,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter);
},
get trustState() {
checkProposedApiEnabled(extension);
checkRequiresWorkspaceTrust(extension);
return extHostWorkspace.trustState;
get isTrusted() {
return extHostWorkspace.trusted;
},
requireWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => {
requestWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => {
checkProposedApiEnabled(extension);
checkRequiresWorkspaceTrust(extension);
return extHostWorkspace.requireWorkspaceTrust(options);
return extHostWorkspace.requestWorkspaceTrust(options);
},
onDidChangeWorkspaceTrustState: (listener, thisArgs?, disposables?) => {
return extHostWorkspace.onDidChangeWorkspaceTrustState(listener, thisArgs, disposables);
onDidGrantWorkspaceTrust: (listener, thisArgs?, disposables?) => {
return extHostWorkspace.onDidGrantWorkspaceTrust(listener, thisArgs, disposables);
}
};
@@ -1045,26 +1058,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
get notebookDocuments(): vscode.NotebookDocument[] {
checkProposedApiEnabled(extension);
return extHostNotebook.notebookDocuments.map(d => d.notebookDocument);
},
get onDidChangeActiveNotebookKernel() {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeActiveNotebookKernel;
return extHostNotebook.notebookDocuments.map(d => d.apiNotebook);
},
registerNotebookSerializer(viewType, serializer, options) {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options);
},
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: {
transientOutputs: boolean;
transientMetadata: { [K in keyof vscode.NotebookCellMetadata]?: boolean }
}) => {
registerNotebookContentProvider: (viewType, provider, options) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options);
},
registerNotebookKernelProvider: (selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) => {
registerNotebookCellStatusBarItemProvider: (selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider);
return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, selector, provider);
},
createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType {
checkProposedApiEnabled(extension);
@@ -1094,13 +1100,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector);
},
createCellStatusBarItem(cell: vscode.NotebookCell, alignment?: vscode.NotebookCellStatusBarAlignment, priority?: number): vscode.NotebookCellStatusBarItem {
checkProposedApiEnabled(extension);
return extHostNotebook.createNotebookCellStatusBarItemInternal(cell, alignment, priority);
},
createNotebookCellExecutionTask(uri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined {
checkProposedApiEnabled(extension);
return extHostNotebook.createNotebookCellExecution(uri, index, kernelId);
},
createNotebookController(id, viewType, label, executeHandler, preloads) {
checkProposedApiEnabled(extension);
return extHostNotebookKernels.createNotebookController(extension, id, viewType, label, executeHandler, preloads);
}
};
@@ -1233,11 +1239,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
InlineHint: extHostTypes.InlineHint,
InlineHintKind: extHostTypes.InlineHintKind,
RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError,
RemoteTrustOption: RemoteTrustOption,
ResolvedAuthority: extHostTypes.ResolvedAuthority,
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
ExtensionRuntime: extHostTypes.ExtensionRuntime,
TimelineItem: extHostTypes.TimelineItem,
NotebookCellRange: extHostTypes.NotebookCellRange,
NotebookRange: extHostTypes.NotebookRange,
NotebookCellKind: extHostTypes.NotebookCellKind,
NotebookCellExecutionState: extHostTypes.NotebookCellExecutionState,
NotebookDocumentMetadata: extHostTypes.NotebookDocumentMetadata,
@@ -1248,11 +1255,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType,
NotebookCellOutput: extHostTypes.NotebookCellOutput,
NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem,
NotebookCellStatusBarItem: extHostTypes.NotebookCellStatusBarItem,
NotebookControllerAffinity: extHostTypes.NotebookControllerAffinity,
LinkedEditingRanges: extHostTypes.LinkedEditingRanges,
TestItem: extHostTypes.TestItem,
TestState: extHostTypes.TestState,
TestResult: extHostTypes.TestResult,
TestItemStatus: extHostTypes.TestItemStatus,
TestResultState: extHostTypes.TestResultState,
TestMessage: extHostTypes.TestMessage,
TextSearchCompleteMessageType: TextSearchCompleteMessageType,
TestMessageSeverity: extHostTypes.TestMessageSeverity,
WorkspaceTrustState: extHostTypes.WorkspaceTrustState
};

View File

@@ -41,7 +41,7 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks';
import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views';
import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ActivationKind, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { ActivationKind, MissingExtensionDependency, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import * as search from 'vs/workbench/services/search/common/search';
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
@@ -50,18 +50,21 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, TransientCellMetadata, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation, INotebookCellStatusBarItem, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
import { DebugConfigurationProviderTriggerKind, TestResultState } from 'vs/workbench/api/common/extHostTypes';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
import { InternalTestItem, ITestState, RunTestForProviderRequest, RunTestsRequest, TestIdWithSrc, TestsDiff, ISerializedTestResults } from 'vs/workbench/contrib/testing/common/testCollection';
import { InternalTestItem, RunTestForProviderRequest, RunTestsRequest, TestIdWithSrc, TestsDiff, ISerializedTestResults, ITestMessage, ITestItem, ITestRunTask, ExtensionRunTestsRequest } from 'vs/workbench/contrib/testing/common/testCollection';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { WorkspaceTrustRequestOptions, WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust';
import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal';
import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions, ITerminalEnvironment, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal';
import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector';
import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@@ -454,7 +457,9 @@ export interface TerminalLaunchConfig {
shellPath?: string;
shellArgs?: string[] | string;
cwd?: string | UriComponents;
env?: { [key: string]: string | null; };
env?: ITerminalEnvironment;
icon?: string;
initialText?: string;
waitOnExit?: boolean;
strictEnv?: boolean;
hideFromUser?: boolean;
@@ -671,10 +676,39 @@ export interface CustomTextEditorCapabilities {
readonly supportsMove?: boolean;
}
export const enum WebviewMessageArrayBufferViewType {
Int8Array = 1,
Uint8Array = 2,
Uint8ClampedArray = 3,
Int16Array = 4,
Uint16Array = 5,
Int32Array = 6,
Uint32Array = 7,
Float32Array = 8,
Float64Array = 9,
BigInt64Array = 10,
BigUint64Array = 11,
}
export interface WebviewMessageArrayBufferReference {
readonly $$vscode_array_buffer_reference$$: true,
readonly index: number;
/**
* Tracks if the reference is to a view instead of directly to an ArrayBuffer.
*/
readonly view?: {
readonly type: WebviewMessageArrayBufferViewType;
readonly byteLength: number;
readonly byteOffset: number;
};
}
export interface MainThreadWebviewsShape extends IDisposable {
$setHtml(handle: WebviewHandle, value: string): void;
$setOptions(handle: WebviewHandle, options: IWebviewOptions): void;
$postMessage(handle: WebviewHandle, value: any): Promise<boolean>
$postMessage(handle: WebviewHandle, value: any, ...buffers: VSBuffer[]): Promise<boolean>
}
export interface MainThreadWebviewPanelsShape extends IDisposable {
@@ -686,6 +720,7 @@ export interface MainThreadWebviewPanelsShape extends IDisposable {
title: string;
webviewOptions: IWebviewOptions;
panelOptions: IWebviewPanelOptions;
serializeBuffersForPostMessage: boolean;
},
showOptions: WebviewPanelShowOptions,
): void;
@@ -694,13 +729,13 @@ export interface MainThreadWebviewPanelsShape extends IDisposable {
$setTitle(handle: WebviewHandle, value: string): void;
$setIconPath(handle: WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void;
$registerSerializer(viewType: string): void;
$registerSerializer(viewType: string, options: { serializeBuffersForPostMessage: boolean }): void;
$unregisterSerializer(viewType: string): void;
}
export interface MainThreadCustomEditorsShape extends IDisposable {
$registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void;
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void;
$registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities, serializeBuffersForPostMessage: boolean): void;
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean, serializeBuffersForPostMessage: boolean): void;
$unregisterEditorProvider(viewType: string): void;
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
@@ -708,7 +743,7 @@ export interface MainThreadCustomEditorsShape extends IDisposable {
}
export interface MainThreadWebviewViewsShape extends IDisposable {
$registerWebviewViewProvider(extension: WebviewExtensionDescription, viewType: string, options?: { retainContextWhenHidden?: boolean }): void;
$registerWebviewViewProvider(extension: WebviewExtensionDescription, viewType: string, options: { retainContextWhenHidden?: boolean, serializeBuffersForPostMessage: boolean }): void;
$unregisterWebviewViewProvider(viewType: string): void;
$setWebviewViewTitle(handle: WebviewHandle, value: string | undefined): void;
@@ -726,7 +761,7 @@ export interface WebviewPanelViewStateData {
}
export interface ExtHostWebviewsShape {
$onMessage(handle: WebviewHandle, message: any): void;
$onMessage(handle: WebviewHandle, jsonSerializedMessage: string, ...buffers: VSBuffer[]): void;
$onMissingCsp(handle: WebviewHandle, extensionId: string): void;
}
@@ -827,37 +862,69 @@ export interface INotebookDocumentShowOptions {
position?: EditorGroupColumn;
preserveFocus?: boolean;
pinned?: boolean;
selection?: ICellRange;
selections?: ICellRange[];
}
export type INotebookCellStatusBarEntryDto = Dto<INotebookCellStatusBarEntry>;
export type INotebookCellStatusBarEntryDto = Dto<INotebookCellStatusBarItem>;
export interface INotebookCellStatusBarListDto {
items: INotebookCellStatusBarEntryDto[];
cacheId: number;
}
export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: {
transientOutputs: boolean;
transientMetadata: TransientMetadata;
transientCellMetadata: TransientCellMetadata;
transientDocumentMetadata: TransientDocumentMetadata;
viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; };
}): Promise<void>;
$updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientMetadata: TransientMetadata; }): Promise<void>;
$updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata; }): Promise<void>;
$unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void;
$unregisterNotebookSerializer(handle: number): void;
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
$onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void;
$trySaveDocument(uri: UriComponents): Promise<boolean>;
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean>;
$applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise<void>;
$postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean>;
$setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise<void>;
$tryOpenDocument(uriComponents: UriComponents): Promise<UriComponents>;
$registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, selector: NotebookSelector): Promise<void>;
$unregisterNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined): Promise<void>;
$emitCellStatusBarEvent(eventHandle: number): void;
}
export interface MainThreadNotebookEditorsShape extends IDisposable {
$tryShowNotebookDocument(uriComponents: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise<string>;
$tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise<void>;
$registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void;
$removeNotebookEditorDecorationType(key: string): void;
$trySetDecorations(id: string, range: ICellRange, decorationKey: string): void;
$tryApplyEdits(editorId: string, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise<boolean>
}
export interface MainThreadNotebookDocumentsShape extends IDisposable {
$tryOpenDocument(uriComponents: UriComponents): Promise<UriComponents>;
$trySaveDocument(uri: UriComponents): Promise<boolean>;
$applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise<void>;
}
export interface INotebookKernelDto2 {
id: string;
viewType: string;
extensionId: ExtensionIdentifier;
extensionLocation: UriComponents;
label: string;
detail?: string;
description?: string;
supportedLanguages?: string[];
supportsInterrupt?: boolean;
hasExecutionOrder?: boolean;
preloads?: { uri: UriComponents; provides: string[] }[];
}
export interface MainThreadNotebookKernelsShape extends IDisposable {
$postMessage(handle: number, editorId: string | undefined, message: any): Promise<boolean>;
$addKernel(handle: number, data: INotebookKernelDto2): Promise<void>;
$updateKernel(handle: number, data: Partial<INotebookKernelDto2>): void;
$removeKernel(handle: number): void;
$updateNotebookPriority(handle: number, uri: UriComponents, value: number | undefined): void;
}
export interface MainThreadUrlsShape extends IDisposable {
@@ -891,7 +958,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
$saveAll(includeUntitled?: boolean): Promise<boolean>;
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise<void>;
$resolveProxy(url: string): Promise<string | undefined>;
$requireWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<WorkspaceTrustState>;
$requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined>;
}
export interface IFileChangeDto {
@@ -945,7 +1012,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
$activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
$onWillActivateExtension(extensionId: ExtensionIdentifier): Promise<void>;
$onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: SerializedError, missingExtensionDependency: MissingExtensionDependency | null): Promise<void>;
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
$setPerformanceMarks(marks: performance.PerformanceMark[]): Promise<void>;
}
@@ -999,6 +1066,8 @@ export interface MainThreadSCMShape extends IDisposable {
$setInputBoxValue(sourceControlHandle: number, value: string): void;
$setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void;
$setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void;
$setInputBoxFocus(sourceControlHandle: number): void;
$showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType): void;
$setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void;
}
@@ -1059,6 +1128,7 @@ export enum CandidatePortSource {
export interface PortAttributesProviderSelector {
pid?: number;
portRange?: [number, number];
commandMatcher?: RegExp;
}
export interface MainThreadTunnelServiceShape extends IDisposable {
@@ -1167,10 +1237,10 @@ export interface ExtHostTreeViewsShape {
}
export interface ExtHostWorkspaceShape {
$initializeWorkspace(workspace: IWorkspaceData | null, trustState: WorkspaceTrustState): void;
$initializeWorkspace(workspace: IWorkspaceData | null, trusted: boolean): void;
$acceptWorkspaceData(workspace: IWorkspaceData | null): void;
$handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void;
$onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void;
$onDidGrantWorkspaceTrust(): void;
}
export interface ExtHostFileSystemInfoShape {
@@ -1268,8 +1338,8 @@ export interface IWillRunFileOperationParticipation {
export interface ExtHostFileSystemEventServiceShape {
$onFileEvent(events: FileSystemEvents): void;
$onWillRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined>;
$onDidRunFileOperation(operation: files.FileOperation, files: SourceTargetPair[]): void;
$onWillRunFileOperation(operation: files.FileOperation, files: readonly SourceTargetPair[], timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined>;
$onDidRunFileOperation(operation: files.FileOperation, files: readonly SourceTargetPair[]): void;
}
export interface ObjectIdentifier {
@@ -1609,15 +1679,6 @@ export interface ExtHostTelemetryShape {
$onDidChangeTelemetryEnabled(enabled: boolean): void;
}
export interface IShellLaunchConfigDto {
name?: string;
executable?: string;
args?: string[] | string;
cwd?: string | UriComponents;
env?: { [key: string]: string | null; };
hideFromUser?: boolean;
}
export interface IShellAndArgsDto {
shell: string;
args: string[] | string | undefined;
@@ -1656,9 +1717,7 @@ export interface ExtHostTerminalServiceShape {
$acceptProcessRequestInitialCwd(id: number): void;
$acceptProcessRequestCwd(id: number): void;
$acceptProcessRequestLatency(id: number): number;
$acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
// TODO: Change quickLaunchOnly to "includeAutoDetected" or something similar
$getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]>;
$getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]>;
$getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
$provideLinks(id: number, line: string): Promise<ITerminalLinkDto[]>;
$activateLink(id: number, linkId: number): void;
@@ -1857,34 +1916,47 @@ export interface INotebookKernelInfoDto2 {
description?: string;
detail?: string;
isPreferred?: boolean;
preloads?: UriComponents[];
preloads?: { uri: UriComponents; provides: string[] }[];
supportedLanguages?: string[]
implementsInterrupt?: boolean;
}
export interface ExtHostNotebookShape {
$resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise<void>;
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined }): void;
$provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]>;
$resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void>;
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRanges: ICellRange[]): Promise<void>;
$cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void>;
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
export interface ExtHostNotebookShape extends ExtHostNotebookDocumentsAndEditorsShape, ExtHostNotebookDocumentsShape, ExtHostNotebookEditorsShape {
$provideNotebookCellStatusBarItems(handle: number, uri: UriComponents, index: number, token: CancellationToken): Promise<INotebookCellStatusBarListDto | undefined>;
$releaseNotebookCellStatusBarItems(id: number): void;
$openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto>;
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
$backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string>;
$dataToNotebook(handle: number, data: VSBuffer): Promise<NotebookDataDto>;
$notebookToData(handle: number, data: NotebookDataDto): Promise<VSBuffer>;
$dataToNotebook(handle: number, data: VSBuffer, token: CancellationToken): Promise<NotebookDataDto>;
$notebookToData(handle: number, data: NotebookDataDto, token: CancellationToken): Promise<VSBuffer>;
}
export interface ExtHostNotebookDocumentsAndEditorsShape {
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
}
export interface ExtHostNotebookDocumentsShape {
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void;
$acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void;
$acceptModelSaved(uriComponents: UriComponents): void;
$acceptDocumentPropertiesChanged(uriComponents: UriComponents, data: INotebookDocumentPropertiesChangeData): void;
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
}
export type INotebookEditorViewColumnInfo = Record<string, number>;
export interface ExtHostNotebookEditorsShape {
$acceptEditorPropertiesChanged(id: string, data: INotebookEditorPropertiesChangeData): void;
$acceptEditorViewColumns(data: INotebookEditorViewColumnInfo): void;
}
export interface ExtHostNotebookKernelsShape {
$acceptSelection(handle: number, uri: UriComponents, value: boolean): void;
$executeCells(handle: number, uri: UriComponents, handles: number[]): Promise<void>;
$cancelCells(handle: number, uri: UriComponents, handles: number[]): Promise<void>;
$acceptRendererMessage(handle: number, editorId: string, message: any): void;
}
export interface ExtHostStorageShape {
@@ -1927,14 +1999,39 @@ export interface ExtHostTestingShape {
}
export interface MainThreadTestingShape {
$registerTestProvider(id: string): void;
$unregisterTestProvider(id: string): void;
/** Registeres that there's a test controller with the given ID */
$registerTestController(id: string): void;
/** Diposes of the test controller with the given ID */
$unregisterTestController(id: string): void;
/** Requests tests from the given resource/uri, from the observer API. */
$subscribeToDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
/** Stops requesting tests from the given resource/uri, from the observer API. */
$unsubscribeFromDiffs(resource: ExtHostTestingResource, uri: UriComponents): void;
/** Publishes that new tests were available on the given source. */
$publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
$updateTestStateInRun(runId: string, testId: string, state: ITestState): void;
/** Request by an extension to run tests. */
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<string>;
$publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void;
// --- test run handling:
/**
* Adds tests to the run. The tests are given in descending depth. The first
* item will be a previously-known test, or a test root.
*/
$addTestsToRun(runId: string, tests: ITestItem[]): void;
/** Updates the state of a test run in the given run. */
$updateTestStateInRun(runId: string, taskId: string, testId: string, state: TestResultState, duration?: number): void;
/** Appends a message to a test in the run. */
$appendTestMessageInRun(runId: string, taskId: string, testId: string, message: ITestMessage): void;
/** Appends raw output to the test run.. */
$appendOutputToRun(runId: string, taskId: string, output: VSBuffer): void;
/** Signals a task in a test run started. */
$startedTestRunTask(runId: string, task: ITestRunTask): void;
/** Signals a task in a test run ended. */
$finishedTestRunTask(runId: string, taskId: string): void;
/** Start a new extension-provided test run. */
$startedExtensionTestRun(req: ExtensionRunTestsRequest): void;
/** Signals that an extension-provided test run finished. */
$finishedExtensionTestRun(runId: string): void;
}
// --- proxy identifiers
@@ -1987,6 +2084,9 @@ export const MainContext = {
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook'),
MainThreadNotebookDocuments: createMainId<MainThreadNotebookDocumentsShape>('MainThreadNotebookDocumentsShape'),
MainThreadNotebookEditors: createMainId<MainThreadNotebookEditorsShape>('MainThreadNotebookEditorsShape'),
MainThreadNotebookKernels: createMainId<MainThreadNotebookKernelsShape>('MainThreadNotebookKernels'),
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline'),
@@ -2033,6 +2133,7 @@ export const ExtHostContext = {
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
ExtHostNotebook: createMainId<ExtHostNotebookShape>('ExtHostNotebook'),
ExtHostNotebookKernels: createMainId<ExtHostNotebookKernelsShape>('ExtHostNotebookKernels'),
ExtHostTheming: createMainId<ExtHostThemingShape>('ExtHostTheming'),
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
ExtHostAuthentication: createMainId<ExtHostAuthenticationShape>('ExtHostAuthentication'),

View File

@@ -18,7 +18,7 @@ import { ICommandsExecutor, RemoveFromRecentlyOpenedAPICommand, OpenIssueReporte
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IRange } from 'vs/editor/common/core/range';
import { IPosition } from 'vs/editor/common/core/position';
import { TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { TransientCellMetadata, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { VSBuffer } from 'vs/base/common/buffer';
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
@@ -30,13 +30,13 @@ const newCommands: ApiCommand[] = [
new ApiCommand(
'vscode.executeDocumentHighlights', '_executeDocumentHighlights', 'Execute document highlight provider.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
new ApiCommandResult<modes.DocumentHighlight[], types.DocumentHighlight[] | undefined>('A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.', tryMapWith(typeConverters.DocumentHighlight.to))
new ApiCommandResult<modes.DocumentHighlight[], types.DocumentHighlight[] | undefined>('A promise that resolves to an array of DocumentHighlight-instances.', tryMapWith(typeConverters.DocumentHighlight.to))
),
// -- document symbols
new ApiCommand(
'vscode.executeDocumentSymbolProvider', '_executeDocumentSymbolProvider', 'Execute document symbol provider.',
[ApiCommandArgument.Uri],
new ApiCommandResult<modes.DocumentSymbol[], vscode.SymbolInformation[] | undefined>('A promise that resolves to an array of DocumentHighlight-instances.', (value, apiArgs) => {
new ApiCommandResult<modes.DocumentSymbol[], vscode.SymbolInformation[] | undefined>('A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.', (value, apiArgs) => {
if (isFalsyOrEmpty(value)) {
return undefined;
@@ -60,7 +60,7 @@ const newCommands: ApiCommand[] = [
range!: vscode.Range;
selectionRange!: vscode.Range;
children!: vscode.DocumentSymbol[];
containerName!: string;
override containerName!: string;
}
return value.map(MergedInfo.to);
@@ -343,18 +343,22 @@ const newCommands: ApiCommand[] = [
new ApiCommandResult<{
viewType: string;
displayName: string;
options: { transientOutputs: boolean; transientMetadata: TransientMetadata };
options: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata; };
filenamePattern: (string | types.RelativePattern | { include: string | types.RelativePattern, exclude: string | types.RelativePattern })[]
}[], {
viewType: string;
displayName: string;
filenamePattern: vscode.NotebookFilenamePattern[];
filenamePattern: (vscode.GlobPattern | { include: vscode.GlobPattern; exclude: vscode.GlobPattern; })[];
options: vscode.NotebookDocumentContentOptions;
}[] | undefined>('A promise that resolves to an array of NotebookContentProvider static info objects.', tryMapWith(item => {
return {
viewType: item.viewType,
displayName: item.displayName,
options: { transientOutputs: item.options.transientOutputs, transientMetadata: item.options.transientMetadata },
options: {
transientOutputs: item.options.transientOutputs,
transientCellMetadata: item.options.transientCellMetadata,
transientDocumentMetadata: item.options.transientDocumentMetadata
},
filenamePattern: item.filenamePattern.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.to(pattern))
};
}))

View File

@@ -14,7 +14,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
import { ExtHostWebviews, shouldSerializeBuffersForPostMessage, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
import { EditorGroupColumn } from 'vs/workbench/common/editor';
import type * as vscode from 'vscode';
@@ -183,7 +183,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, {
supportsMove: !!provider.moveCustomTextEditor,
});
}, shouldSerializeBuffersForPostMessage(extension));
} else {
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
@@ -199,7 +199,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
}));
}
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument);
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument, shouldSerializeBuffersForPostMessage(extension));
}
return extHostTypes.Disposable.from(

View File

@@ -384,7 +384,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
}
};
}
return this._variableResolver.resolveAny(ws, config);
return this._variableResolver.resolveAnyAsync(ws, config);
}
protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined {
@@ -990,7 +990,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ
}
return undefined;
}
}, undefined, process.env);
}, undefined, Promise.resolve(process.env));
}
}

View File

@@ -288,7 +288,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, loggingProxy, _onDidChangeDiagnostics);
_collections.set(owner, this);
}
dispose() {
override dispose() {
super.dispose();
_collections.delete(owner);
}

View File

@@ -42,7 +42,7 @@ export class ExtHostDocumentData extends MirrorTextModel {
super(uri, lines, eol, versionId);
}
dispose(): void {
override dispose(): void {
// we don't really dispose documents but let
// extensions still read from them. some
// operations, live saving, will now error tho

View File

@@ -3,12 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import type * as vscode from 'vscode';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/services/extensions/common/extensions';
import { MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@@ -158,7 +157,7 @@ export class FailedExtension extends ActivatedExtension {
}
export interface IExtensionsActivatorHost {
onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): void;
onExtensionActivationError(extensionId: ExtensionIdentifier, error: Error | null, missingExtensionDependency: MissingExtensionDependency | null): void;
actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension>;
}
@@ -255,8 +254,12 @@ export class ExtensionsActivator {
const currentExtension = this._registry.getExtensionDescription(currentActivation.id);
if (!currentExtension) {
// Error condition 0: unknown extension
this._host.onExtensionActivationError(currentActivation.id, new MissingDependencyError(currentActivation.id.value));
const error = new Error(`Unknown dependency '${currentActivation.id.value}'`);
const error = new Error(`Cannot activate unknown extension '${currentActivation.id.value}'`);
this._host.onExtensionActivationError(
currentActivation.id,
error,
new MissingExtensionDependency(currentActivation.id.value)
);
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentActivation.id), new FailedExtension(error));
return;
}
@@ -280,9 +283,16 @@ export class ExtensionsActivator {
if (dep && dep.activationFailed) {
// Error condition 2: a dependency has already failed activation
this._host.onExtensionActivationError(currentExtension.identifier, nls.localize('failedDep1', "Cannot activate extension '{0}' because it depends on extension '{1}', which failed to activate.", currentExtension.displayName || currentExtension.identifier.value, depId));
const error = new Error(`Dependency ${depId} failed to activate`);
const currentExtensionFriendlyName = currentExtension.displayName || currentExtension.identifier.value;
const depDesc = this._registry.getExtensionDescription(depId);
const depFriendlyName = (depDesc ? depDesc.displayName || depId : depId);
const error = new Error(`Cannot activate the '${currentExtensionFriendlyName}' extension because its dependency '${depFriendlyName}' failed to activate`);
(<any>error).detail = dep.activationFailedError;
this._host.onExtensionActivationError(
currentExtension.identifier,
error,
null
);
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
return;
}
@@ -309,8 +319,13 @@ export class ExtensionsActivator {
}
// Error condition 1: unknown dependency
this._host.onExtensionActivationError(currentExtension.identifier, new MissingDependencyError(depId));
const error = new Error(`Unknown dependency '${depId}'`);
const currentExtensionFriendlyName = currentExtension.displayName || currentExtension.identifier.value;
const error = new Error(`Cannot activate the '${currentExtensionFriendlyName}' extension because it depends on unknown extension '${depId}'`);
this._host.onExtensionActivationError(
currentExtension.identifier,
error,
new MissingExtensionDependency(depId)
);
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
return;
}
@@ -372,7 +387,25 @@ export class ExtensionsActivator {
}
const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => {
this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message));
const error = new Error();
if (err && err.name) {
error.name = err.name;
}
if (err && err.message) {
error.message = `Activating extension '${extensionId.value}' failed: ${err.message}.`;
} else {
error.message = `Activating extension '${extensionId.value}' failed: ${err}.`;
}
if (err && err.stack) {
error.stack = err.stack;
}
this._host.onExtensionActivationError(
extensionId,
error,
null
);
this._logService.error(`Activating extension ${extensionId.value} failed due to an error:`);
this._logService.error(err);
// Treat the extension as being empty

View File

@@ -17,12 +17,11 @@ import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/co
import { ActivatedExtension, EmptyExtension, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator';
import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { ExtensionActivationError, checkProposedApiEnabled, ActivationKind } from 'vs/workbench/services/extensions/common/extensions';
import { MissingExtensionDependency, checkProposedApiEnabled, ActivationKind } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import * as errors from 'vs/base/common/errors';
import type * as vscode from 'vscode';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { VSBuffer } from 'vs/base/common/buffer';
import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
@@ -161,8 +160,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
this._initData.resolvedExtensions,
this._initData.hostExtensions,
{
onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => {
this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error);
onExtensionActivationError: (extensionId: ExtensionIdentifier, error: Error, missingExtensionDependency: MissingExtensionDependency | null): void => {
this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, errors.transformErrorForSerialization(error), missingExtensionDependency);
},
actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> => {
@@ -561,17 +560,15 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
private async _doHandleExtensionTests(): Promise<number> {
const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment;
if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI || extensionTestsLocationURI.scheme !== Schemas.file) {
if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI) {
throw new Error(nls.localize('extensionTestError1', "Cannot load test runner."));
}
const extensionTestsPath = originalFSPath(extensionTestsLocationURI);
// Require the test runner via node require from the provided path
const testRunner: ITestRunner | INewTestRunner | undefined = await this._loadCommonJSModule(null, extensionTestsLocationURI, new ExtensionActivationTimesBuilder(false));
if (!testRunner || typeof testRunner.run !== 'function') {
throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath));
throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsLocationURI.toString()));
}
// Execute the runner if it follows the old `run` spec
@@ -584,6 +581,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
}
};
const extensionTestsPath = originalFSPath(extensionTestsLocationURI); // for the old test runner API
const runResult = testRunner.run(extensionTestsPath, oldTestRunnerCallback);
// Using the new API `run(): Promise<void>`
@@ -655,10 +654,10 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
}
try {
this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver));
performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`);
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver));
// Split merged API result into separate authority/options
const authority: ResolvedAuthority = {
@@ -668,7 +667,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
connectionToken: result.connectionToken
};
const options: ResolvedOptions = {
extensionHostEnv: result.extensionHostEnv
extensionHostEnv: result.extensionHostEnv,
trust: result.trust
};
return {

View File

@@ -7,6 +7,7 @@ import type * as vscode from 'vscode';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async';
export class ExtensionMemento implements vscode.Memento {
@@ -18,6 +19,9 @@ export class ExtensionMemento implements vscode.Memento {
private _value?: { [n: string]: any; };
private readonly _storageListener: IDisposable;
private _deferredPromises: Map<string, DeferredPromise<void>> = new Map();
private _scheduler: RunOnceScheduler;
constructor(id: string, global: boolean, storage: ExtHostStorage) {
this._id = id;
this._shared = global;
@@ -33,6 +37,23 @@ export class ExtensionMemento implements vscode.Memento {
this._value = e.value;
}
});
this._scheduler = new RunOnceScheduler(() => {
const records = this._deferredPromises;
this._deferredPromises = new Map();
(async () => {
try {
await this._storage.setValue(this._shared, this._id, this._value!);
for (const value of records.values()) {
value.complete();
}
} catch (e) {
for (const value of records.values()) {
value.error(e);
}
}
})();
}, 0);
}
get whenReady(): Promise<ExtensionMemento> {
@@ -51,7 +72,20 @@ export class ExtensionMemento implements vscode.Memento {
update(key: string, value: any): Promise<void> {
this._value![key] = value;
return this._storage.setValue(this._shared, this._id, this._value!);
let record = this._deferredPromises.get(key);
if (record !== undefined) {
return record.p;
}
const promise = new DeferredPromise<void>();
this._deferredPromises.set(key, promise);
if (!this._scheduler.isScheduled()) {
this._scheduler.schedule();
}
return promise.p;
}
dispose(): void {

File diff suppressed because it is too large Load Diff

View File

@@ -73,7 +73,7 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD
this._cellUris = new ResourceMap();
const cellLengths: number[] = [];
const cellLineCounts: number[] = [];
for (const cell of this._notebook.cells) {
for (const cell of this._notebook.getCells()) {
if (cell.kind === types.NotebookCellKind.Code && (!this._selector || score(this._selector, cell.document.uri, cell.document.languageId, true))) {
this._cellUris.set(cell.document.uri, this._cells.length);
this._cells.push(cell);

View File

@@ -3,12 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { deepFreeze, equals } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookDocumentsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
@@ -26,7 +24,7 @@ class RawContentChangeEvent {
start: event.start,
deletedCount: event.deletedCount,
deletedItems: event.deletedItems,
items: event.items.map(data => data.cell)
items: event.items.map(data => data.apiCell)
};
});
}
@@ -46,9 +44,6 @@ export class ExtHostCell {
};
}
private _onDidDispose = new Emitter<void>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
private _outputs: extHostTypes.NotebookCellOutput[];
private _metadata: extHostTypes.NotebookCellMetadata;
private _previousResult: vscode.NotebookCellExecutionSummary | undefined;
@@ -74,16 +69,11 @@ export class ExtHostCell {
this._previousResult = extHostTypeConverters.NotebookCellPreviousExecutionResult.to(this._internalMetadata);
}
dispose() {
this._onDidDispose.fire();
this._onDidDispose.dispose();
}
get internalMetadata(): NotebookCellMetadata {
return this._internalMetadata;
}
get cell(): vscode.NotebookCell {
get apiCell(): vscode.NotebookCell {
if (!this._cell) {
const that = this;
const data = this._extHostDocument.getDocument(this.uri);
@@ -92,7 +82,7 @@ export class ExtHostCell {
}
this._cell = Object.freeze<vscode.NotebookCell>({
get index() { return that._notebook.getCellIndex(that); },
notebook: that._notebook.notebookDocument,
notebook: that._notebook.apiNotebook,
kind: extHostTypeConverters.NotebookCellKind.to(this._cellData.cellKind),
document: data.document,
get outputs() { return that._outputs.slice(0); },
@@ -133,15 +123,13 @@ export interface INotebookEventEmitter {
}
export class ExtHostNotebookDocument extends Disposable {
export class ExtHostNotebookDocument {
private static _handlePool: number = 0;
readonly handle = ExtHostNotebookDocument._handlePool++;
private _cells: ExtHostCell[] = [];
private _cellDisposableMapping = new Map<number, DisposableStore>();
private _notebook: vscode.NotebookDocument | undefined;
private _versionId: number = 0;
private _isDirty: boolean = false;
@@ -149,39 +137,43 @@ export class ExtHostNotebookDocument extends Disposable {
private _disposed: boolean = false;
constructor(
private readonly _proxy: MainThreadNotebookShape,
private readonly _proxy: MainThreadNotebookDocumentsShape,
private readonly _textDocumentsAndEditors: ExtHostDocumentsAndEditors,
private readonly _textDocuments: ExtHostDocuments,
private readonly _emitter: INotebookEventEmitter,
private readonly _viewType: string,
private _metadata: extHostTypes.NotebookDocumentMetadata,
readonly uri: URI,
) {
super();
}
) { }
dispose() {
this._disposed = true;
super.dispose();
dispose(this._cellDisposableMapping.values());
}
get notebookDocument(): vscode.NotebookDocument {
get apiNotebook(): vscode.NotebookDocument {
if (!this._notebook) {
const that = this;
this._notebook = Object.freeze({
this._notebook = {
get uri() { return that.uri; },
get version() { return that._versionId; },
get fileName() { return that.uri.fsPath; },
get viewType() { return that._viewType; },
get isDirty() { return that._isDirty; },
get isUntitled() { return that.uri.scheme === Schemas.untitled; },
get cells(): ReadonlyArray<vscode.NotebookCell> { return that._cells.map(cell => cell.cell); },
get isClosed() { return that._disposed; },
get metadata() { return that._metadata; },
set metadata(_value: Required<vscode.NotebookDocumentMetadata>) { throw new Error('Use WorkspaceEdit to update metadata.'); },
save() { return that._save(); }
});
get cellCount() { return that._cells.length; },
cellAt(index) {
index = that._validateIndex(index);
return that._cells[index].apiCell;
},
getCells(range) {
const cells = range ? that._getCells(range) : that._cells;
return cells.map(cell => cell.apiCell);
},
save() {
return that._save();
}
};
}
return this._notebook;
}
@@ -225,6 +217,35 @@ export class ExtHostNotebookDocument extends Disposable {
}
}
private _validateIndex(index: number): number {
if (index < 0) {
return 0;
} else if (index >= this._cells.length) {
return this._cells.length - 1;
} else {
return index;
}
}
private _validateRange(range: vscode.NotebookRange): vscode.NotebookRange {
if (range.start < 0) {
range = range.with({ start: 0 });
}
if (range.end > this._cells.length) {
range = range.with({ end: this._cells.length });
}
return range;
}
private _getCells(range: vscode.NotebookRange): ExtHostCell[] {
range = this._validateRange(range);
const result: ExtHostCell[] = [];
for (let i = range.start; i < range.end; i++) {
result.push(this._cells[i]);
}
return result;
}
private async _save(): Promise<boolean> {
if (this._disposed) {
return Promise.reject(new Error('Notebook has been closed'));
@@ -246,30 +267,17 @@ export class ExtHostNotebookDocument extends Disposable {
const newCells = cellDtos.map(cell => {
const extCell = new ExtHostCell(this, this._textDocumentsAndEditors, cell);
if (!initialization) {
addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell));
addedCellDocuments.push(ExtHostCell.asModelAddData(this.apiNotebook, cell));
}
if (!this._cellDisposableMapping.has(extCell.handle)) {
const store = new DisposableStore();
store.add(extCell);
this._cellDisposableMapping.set(extCell.handle, store);
}
return extCell;
});
for (let j = splice[0]; j < splice[0] + splice[1]; j++) {
this._cellDisposableMapping.get(this._cells[j].handle)?.dispose();
this._cellDisposableMapping.delete(this._cells[j].handle);
}
const changeEvent = new RawContentChangeEvent(splice[0], splice[1], [], newCells);
const deletedItems = this._cells.splice(splice[0], splice[1], ...newCells);
for (let cell of deletedItems) {
removedCellDocuments.push(cell.uri);
changeEvent.deletedItems.push(cell.cell);
changeEvent.deletedItems.push(cell.apiCell);
}
contentChangeEvents.push(changeEvent);
@@ -282,7 +290,7 @@ export class ExtHostNotebookDocument extends Disposable {
if (!initialization) {
this._emitter.emitModelChange(deepFreeze({
document: this.notebookDocument,
document: this.apiNotebook,
changes: RawContentChangeEvent.asApiEvents(contentChangeEvents)
}));
}
@@ -292,11 +300,11 @@ export class ExtHostNotebookDocument extends Disposable {
const cells = this._cells.splice(index, 1);
this._cells.splice(newIdx, 0, ...cells);
const changes = [
new RawContentChangeEvent(index, 1, cells.map(c => c.cell), []),
new RawContentChangeEvent(index, 1, cells.map(c => c.apiCell), []),
new RawContentChangeEvent(newIdx, 0, [], cells)
];
this._emitter.emitModelChange(deepFreeze({
document: this.notebookDocument,
document: this.apiNotebook,
changes: RawContentChangeEvent.asApiEvents(changes)
}));
}
@@ -304,18 +312,18 @@ export class ExtHostNotebookDocument extends Disposable {
private _setCellOutputs(index: number, outputs: IOutputDto[]): void {
const cell = this._cells[index];
cell.setOutputs(outputs);
this._emitter.emitCellOutputsChange(deepFreeze({ document: this.notebookDocument, cells: [cell.cell] }));
this._emitter.emitCellOutputsChange(deepFreeze({ document: this.apiNotebook, cells: [cell.apiCell] }));
}
private _setCellOutputItems(index: number, outputId: string, append: boolean, outputItems: IOutputItemDto[]): void {
const cell = this._cells[index];
cell.setOutputItems(outputId, append, outputItems);
this._emitter.emitCellOutputsChange(deepFreeze({ document: this.notebookDocument, cells: [cell.cell] }));
this._emitter.emitCellOutputsChange(deepFreeze({ document: this.apiNotebook, cells: [cell.apiCell] }));
}
private _changeCellLanguage(index: number, newModeId: string): void {
const cell = this._cells[index];
if (cell.cell.document.languageId !== newModeId) {
if (cell.apiCell.document.languageId !== newModeId) {
this._textDocuments.$acceptModelModeChanged(cell.uri, newModeId);
}
}
@@ -324,17 +332,17 @@ export class ExtHostNotebookDocument extends Disposable {
const cell = this._cells[index];
const originalInternalMetadata = cell.internalMetadata;
const originalExtMetadata = cell.cell.metadata;
const originalExtMetadata = cell.apiCell.metadata;
cell.setMetadata(newMetadata);
const newExtMetadata = cell.cell.metadata;
const newExtMetadata = cell.apiCell.metadata;
if (!equals(originalExtMetadata, newExtMetadata)) {
this._emitter.emitCellMetadataChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell }));
this._emitter.emitCellMetadataChange(deepFreeze({ document: this.apiNotebook, cell: cell.apiCell }));
}
if (originalInternalMetadata.runState !== newMetadata.runState) {
const executionState = newMetadata.runState ?? extHostTypes.NotebookCellExecutionState.Idle;
this._emitter.emitCellExecutionStateChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell, executionState }));
this._emitter.emitCellExecutionStateChange(deepFreeze({ document: this.apiNotebook, cell: cell.apiCell, executionState }));
}
}

View File

@@ -3,8 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { MainThreadNotebookEditorsShape } from 'vs/workbench/api/common/extHost.protocol';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import * as extHostConverter from 'vs/workbench/api/common/extHostTypeConverters';
import { CellEditType, ICellEditOperation, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -58,17 +57,6 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit {
});
}
replaceCellOutput(index: number, outputs: vscode.NotebookCellOutput[]): void {
this._throwIfFinalized();
this._collectedEdits.push({
editType: CellEditType.Output,
index,
outputs: outputs.map(output => {
return extHostConverter.NotebookCellOutput.from(output);
})
});
}
replaceCells(from: number, to: number, cells: vscode.NotebookCellData[]): void {
this._throwIfFinalized();
if (from === to && cells.length === 0) {
@@ -85,27 +73,21 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit {
export class ExtHostNotebookEditor {
private _selections: vscode.NotebookCellRange[] = [];
private _visibleRanges: vscode.NotebookCellRange[] = [];
private _selections: vscode.NotebookRange[] = [];
private _visibleRanges: vscode.NotebookRange[] = [];
private _viewColumn?: vscode.ViewColumn;
private _visible: boolean = false;
private _kernel?: vscode.NotebookKernel;
private readonly _hasDecorationsForKey = new Set<string>();
private readonly _onDidDispose = new Emitter<void>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
private _editor?: vscode.NotebookEditor;
constructor(
readonly id: string,
private readonly _viewType: string,
private readonly _proxy: MainThreadNotebookShape,
private readonly _proxy: MainThreadNotebookEditorsShape,
readonly notebookData: ExtHostNotebookDocument,
visibleRanges: vscode.NotebookCellRange[],
selections: vscode.NotebookCellRange[],
visibleRanges: vscode.NotebookRange[],
selections: vscode.NotebookRange[],
viewColumn: vscode.ViewColumn | undefined
) {
this._selections = selections;
@@ -113,21 +95,12 @@ export class ExtHostNotebookEditor {
this._viewColumn = viewColumn;
}
dispose() {
this._onDidDispose.fire();
this._onDidDispose.dispose();
}
get editor(): vscode.NotebookEditor {
get apiEditor(): vscode.NotebookEditor {
if (!this._editor) {
const that = this;
this._editor = {
get document() {
return that.notebookData.notebookDocument;
},
get selection() {
const primarySelection = that._selections[0];
return primarySelection && that.notebookData.getCellFromIndex(primarySelection.start)?.cell;
return that.notebookData.apiNotebook;
},
get selections() {
return that._selections;
@@ -138,24 +111,18 @@ export class ExtHostNotebookEditor {
revealRange(range, revealType) {
that._proxy.$tryRevealRange(
that.id,
extHostConverter.NotebookCellRange.from(range),
extHostConverter.NotebookRange.from(range),
revealType ?? extHostTypes.NotebookEditorRevealType.Default
);
},
get viewColumn() {
return that._viewColumn;
},
get onDidDispose() {
return that.onDidDispose;
},
edit(callback) {
const edit = new NotebookEditorCellEditBuilder(this.document.version);
callback(edit);
return that._applyEdit(edit.finalize());
},
get kernel() {
return that._kernel;
},
setDecorations(decorationType, range) {
return that.setDecorations(decorationType, range);
}
@@ -164,10 +131,6 @@ export class ExtHostNotebookEditor {
return this._editor;
}
_acceptKernel(kernel?: vscode.NotebookKernel) {
this._kernel = kernel;
}
get visible(): boolean {
return this._visible;
}
@@ -176,14 +139,18 @@ export class ExtHostNotebookEditor {
this._visible = value;
}
_acceptVisibleRanges(value: vscode.NotebookCellRange[]): void {
_acceptVisibleRanges(value: vscode.NotebookRange[]): void {
this._visibleRanges = value;
}
_acceptSelections(selections: vscode.NotebookCellRange[]): void {
_acceptSelections(selections: vscode.NotebookRange[]): void {
this._selections = selections;
}
_acceptViewColumn(value: vscode.ViewColumn | undefined) {
this._viewColumn = value;
}
private _applyEdit(editData: INotebookEditData): Promise<boolean> {
// return when there is nothing to do
@@ -217,10 +184,10 @@ export class ExtHostNotebookEditor {
compressedEditsIndex++;
}
return this._proxy.$tryApplyEdits(this._viewType, this.notebookData.uri, editData.documentVersionId, compressedEdits);
return this._proxy.$tryApplyEdits(this.id, editData.documentVersionId, compressedEdits);
}
setDecorations(decorationType: vscode.NotebookEditorDecorationType, range: vscode.NotebookCellRange): void {
setDecorations(decorationType: vscode.NotebookEditorDecorationType, range: vscode.NotebookRange): void {
if (range.isEmpty && !this._hasDecorationsForKey.has(decorationType.key)) {
// avoid no-op call to the renderer
return;
@@ -233,7 +200,7 @@ export class ExtHostNotebookEditor {
return this._proxy.$trySetDecorations(
this.id,
extHostConverter.NotebookCellRange.from(range),
extHostConverter.NotebookRange.from(range),
decorationType.key
);
}

View File

@@ -0,0 +1,259 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ExtHostNotebookKernelsShape, IMainContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from 'vs/workbench/api/common/extHost.protocol';
import * as vscode from 'vscode';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { asWebviewUri } from 'vs/workbench/api/common/shared/webview';
interface IKernelData {
extensionId: ExtensionIdentifier,
controller: vscode.NotebookController;
onDidChangeSelection: Emitter<{ selected: boolean; notebook: vscode.NotebookDocument; }>;
onDidReceiveMessage: Emitter<{ editor: vscode.NotebookEditor, message: any }>;
}
export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
private readonly _proxy: MainThreadNotebookKernelsShape;
private readonly _kernelData = new Map<number, IKernelData>();
private _handlePool: number = 0;
constructor(
mainContext: IMainContext,
private readonly _initData: IExtHostInitDataService,
private readonly _extHostNotebook: ExtHostNotebookController
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebookKernels);
}
createNotebookController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler?: vscode.NotebookExecuteHandler, preloads?: vscode.NotebookKernelPreload[]): vscode.NotebookController {
for (let data of this._kernelData.values()) {
if (data.controller.id === id && ExtensionIdentifier.equals(extension.identifier, data.extensionId)) {
throw new Error(`notebook controller with id '${id}' ALREADY exist`);
}
}
const handle = this._handlePool++;
const that = this;
const _defaultExecutHandler = () => console.warn(`NO execute handler from notebook controller '${data.id}' of extension: '${extension.identifier}'`);
let isDisposed = false;
const commandDisposables = new DisposableStore();
const onDidChangeSelection = new Emitter<{ selected: boolean, notebook: vscode.NotebookDocument }>();
const onDidReceiveMessage = new Emitter<{ editor: vscode.NotebookEditor, message: any }>();
const data: INotebookKernelDto2 = {
id: `${extension.identifier.value}/${id}`,
viewType,
extensionId: extension.identifier,
extensionLocation: extension.extensionLocation,
label: label || extension.identifier.value,
preloads: preloads ? preloads.map(extHostTypeConverters.NotebookKernelPreload.from) : []
};
//
let _executeHandler: vscode.NotebookExecuteHandler = handler ?? _defaultExecutHandler;
let _interruptHandler: vscode.NotebookInterruptHandler | undefined;
// todo@jrieken the selector needs to be massaged
this._proxy.$addKernel(handle, data).catch(err => {
// this can happen when a kernel with that ID is already registered
console.log(err);
isDisposed = true;
});
// update: all setters write directly into the dto object
// and trigger an update. the actual update will only happen
// once per event loop execution
let tokenPool = 0;
const _update = () => {
if (isDisposed) {
return;
}
const myToken = ++tokenPool;
Promise.resolve().then(() => {
if (myToken === tokenPool) {
this._proxy.$updateKernel(handle, data);
}
});
};
const controller: vscode.NotebookController = {
get id() { return id; },
get viewType() { return data.viewType; },
onDidChangeNotebookAssociation: onDidChangeSelection.event,
get label() {
return data.label;
},
set label(value) {
data.label = value ?? extension.displayName ?? extension.name;
_update();
},
get detail() {
return data.detail ?? '';
},
set detail(value) {
data.detail = value;
_update();
},
get description() {
return data.description ?? '';
},
set description(value) {
data.description = value;
_update();
},
get supportedLanguages() {
return data.supportedLanguages;
},
set supportedLanguages(value) {
data.supportedLanguages = value;
_update();
},
get hasExecutionOrder() {
return data.hasExecutionOrder ?? false;
},
set hasExecutionOrder(value) {
data.hasExecutionOrder = value;
_update();
},
get preloads() {
return data.preloads ? data.preloads.map(extHostTypeConverters.NotebookKernelPreload.to) : [];
},
get executeHandler() {
return _executeHandler;
},
set executeHandler(value) {
_executeHandler = value ?? _defaultExecutHandler;
},
get interruptHandler() {
return _interruptHandler;
},
set interruptHandler(value) {
_interruptHandler = value;
data.supportsInterrupt = Boolean(value);
_update();
},
createNotebookCellExecutionTask(cell) {
if (isDisposed) {
throw new Error('notebook controller is DISPOSED');
}
//todo@jrieken
return that._extHostNotebook.createNotebookCellExecution(cell.notebook.uri, cell.index, data.id)!;
},
dispose: () => {
if (!isDisposed) {
isDisposed = true;
this._kernelData.delete(handle);
commandDisposables.dispose();
onDidChangeSelection.dispose();
onDidReceiveMessage.dispose();
this._proxy.$removeKernel(handle);
}
},
// --- ipc
onDidReceiveMessage: onDidReceiveMessage.event,
postMessage(message, editor) {
return that._proxy.$postMessage(handle, editor && that._extHostNotebook.getIdByEditor(editor), message);
},
asWebviewUri(uri: URI) {
return asWebviewUri(that._initData.environment, String(handle), uri);
},
// --- priority
updateNotebookAffinity(notebook, priority) {
that._proxy.$updateNotebookPriority(handle, notebook.uri, priority);
}
};
this._kernelData.set(handle, { extensionId: extension.identifier, controller, onDidChangeSelection, onDidReceiveMessage });
return controller;
}
$acceptSelection(handle: number, uri: UriComponents, value: boolean): void {
const obj = this._kernelData.get(handle);
if (obj) {
obj.onDidChangeSelection.fire({
selected: value,
notebook: this._extHostNotebook.lookupNotebookDocument(URI.revive(uri))!.apiNotebook
});
}
}
async $executeCells(handle: number, uri: UriComponents, handles: number[]): Promise<void> {
const obj = this._kernelData.get(handle);
if (!obj) {
// extension can dispose kernels in the meantime
return;
}
const document = this._extHostNotebook.lookupNotebookDocument(URI.revive(uri));
if (!document) {
throw new Error('MISSING notebook');
}
const cells: vscode.NotebookCell[] = [];
for (let cellHandle of handles) {
const cell = document.getCell(cellHandle);
if (cell) {
cells.push(cell.apiCell);
}
}
try {
await obj.controller.executeHandler.call(obj.controller, cells, document.apiNotebook, obj.controller);
} catch (err) {
//
console.error(err);
}
}
async $cancelCells(handle: number, uri: UriComponents, handles: number[]): Promise<void> {
const obj = this._kernelData.get(handle);
if (!obj) {
// extension can dispose kernels in the meantime
return;
}
const document = this._extHostNotebook.lookupNotebookDocument(URI.revive(uri));
if (!document) {
throw new Error('MISSING notebook');
}
if (obj.controller.interruptHandler) {
await obj.controller.interruptHandler.call(obj.controller, document.apiNotebook);
}
// we do both? interrupt and cancellation or should we be selective?
for (let cellHandle of handles) {
const cell = document.getCell(cellHandle);
if (cell) {
this._extHostNotebook.cancelOneNotebookCellExecution(cell);
}
}
}
$acceptRendererMessage(handle: number, editorId: string, message: any): void {
const obj = this._kernelData.get(handle);
if (!obj) {
// extension can dispose kernels in the meantime
return;
}
const editor = this._extHostNotebook.getEditorById(editorId);
if (!editor) {
throw new Error(`send message for UNKNOWN editor: ${editorId}`);
}
obj.onDidReceiveMessage.fire(Object.freeze({ editor: editor.apiEditor, message }));
}
}

View File

@@ -73,7 +73,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements
}
}
dispose(): void {
override dispose(): void {
super.dispose();
if (!this._disposed) {
@@ -90,7 +90,7 @@ export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel {
super(name, false, undefined, proxy);
}
append(value: string): void {
override append(value: string): void {
super.append(value);
this._id.then(id => this._proxy.$append(id, value));
this._onDidAppend.fire();
@@ -103,7 +103,7 @@ class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel {
super(name, true, file, proxy);
}
append(value: string): void {
override append(value: string): void {
throw new Error('Not supported');
}
}

View File

@@ -259,6 +259,22 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox {
// noop
}
focus(): void {
checkProposedApiEnabled(this._extension);
if (!this._visible) {
this.visible = true;
}
this._proxy.$setInputBoxFocus(this._sourceControlHandle);
}
showValidationMessage(message: string, type: vscode.SourceControlInputBoxValidationType) {
checkProposedApiEnabled(this._extension);
this._proxy.$showValidationMessage(this._sourceControlHandle, message, type as any);
}
$onInputBoxValueChange(value: string): void {
this.updateValue(value);
}

View File

@@ -5,7 +5,7 @@
import type * as vscode from 'vscode';
import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
@@ -19,7 +19,7 @@ import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/ter
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { generateUuid } from 'vs/base/common/uuid';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal';
import { IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -117,7 +117,9 @@ export class ExtHostTerminal {
shellPath?: string,
shellArgs?: string[] | string,
cwd?: string | URI,
env?: { [key: string]: string | null },
env?: ITerminalEnvironment,
icon?: string,
initialText?: string,
waitOnExit?: boolean,
strictEnv?: boolean,
hideFromUser?: boolean,
@@ -127,7 +129,7 @@ export class ExtHostTerminal {
if (typeof this._id !== 'string') {
throw new Error('Terminal has already been created');
}
await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal, isExtensionOwnedTerminal });
await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, icon, initialText, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal, isExtensionOwnedTerminal });
}
public async createExtensionTerminal(): Promise<number> {
@@ -225,6 +227,10 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
}
}
async processBinary(data: string): Promise<void> {
// No-op, processBinary is not supported in extextion owned terminals.
}
acknowledgeDataEvent(charCount: number): void {
// No-op, flow control is not supported in extension owned terminals. If this is ever
// implemented it will need new pause and resume VS Code APIs.
@@ -328,9 +334,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
public abstract createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal;
public abstract getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
public abstract getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
public abstract $getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]>;
public abstract $getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]>;
public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal {
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
@@ -771,23 +776,18 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService {
}
public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string {
// Return the empty string to avoid throwing
return '';
throw new NotSupportedError();
}
public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string {
throw new NotSupportedError();
}
public $getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]> {
public $getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]> {
throw new NotSupportedError();
}
public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
throw new NotSupportedError();
}
public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void {
// No-op for web worker ext host as workspace permissions aren't used
}
}

View File

@@ -4,10 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { mapFind } from 'vs/base/common/arrays';
import { disposableTimeout } from 'vs/base/common/async';
import { Barrier, DeferredPromise, disposableTimeout, isThenable } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { once } from 'vs/base/common/functional';
import { Iterable } from 'vs/base/common/iterator';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { deepFreeze } from 'vs/base/common/objects';
import { isDefined } from 'vs/base/common/types';
@@ -17,34 +19,40 @@ import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTes
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostTestItemEventType, getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
import { Disposable, TestItem as TestItemImpl, TestItemHookProperty } from 'vs/workbench/api/common/extHostTypes';
import { Disposable, TestItemImpl } from 'vs/workbench/api/common/extHostTypes';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { OwnedTestCollection, SingleUseTestCollection, TestPosition } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, RunTestForProviderRequest, TestDiffOpType, TestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestItem, RunTestForProviderRequest, TestDiffOpType, TestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import type * as vscode from 'vscode';
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
export class ExtHostTesting implements ExtHostTestingShape {
private readonly resultsChangedEmitter = new Emitter<void>();
private readonly providers = new Map<string, vscode.TestProvider>();
private readonly controllers = new Map<string, {
extensionId: string,
instance: vscode.TestController<unknown>
}>();
private readonly proxy: MainThreadTestingShape;
private readonly ownedTests = new OwnedTestCollection();
private readonly testSubscriptions = new Map<string, {
private readonly runQueue: TestRunQueue;
private readonly testControllers = new Map<string, {
collection: SingleUseTestCollection;
store: IDisposable;
subscribeFn: (id: string, provider: vscode.TestProvider) => void;
subscribeFn: (id: string, provider: vscode.TestController<unknown>) => void;
}>();
private workspaceObservers: WorkspaceFolderTestObserverFactory;
private textDocumentObservers: TextDocumentTestObserverFactory;
public onResultsChanged = this.resultsChangedEmitter.event;
public results: ReadonlyArray<vscode.TestResults> = [];
public results: ReadonlyArray<vscode.TestRunResult> = [];
constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) {
this.proxy = rpc.getProxy(MainContext.MainThreadTesting);
this.runQueue = new TestRunQueue(this.proxy);
this.workspaceObservers = new WorkspaceFolderTestObserverFactory(this.proxy);
this.textDocumentObservers = new TextDocumentTestObserverFactory(this.proxy, documents);
}
@@ -52,22 +60,22 @@ export class ExtHostTesting implements ExtHostTestingShape {
/**
* Implements vscode.test.registerTestProvider
*/
public registerTestProvider<T extends vscode.TestItem>(provider: vscode.TestProvider<T>): vscode.Disposable {
const providerId = generateUuid();
this.providers.set(providerId, provider);
this.proxy.$registerTestProvider(providerId);
public registerTestController<T>(extensionId: string, controller: vscode.TestController<T>): vscode.Disposable {
const controllerId = generateUuid();
this.controllers.set(controllerId, { instance: controller, extensionId });
this.proxy.$registerTestController(controllerId);
// give the ext a moment to register things rather than synchronously invoking within activate()
const toSubscribe = [...this.testSubscriptions.keys()];
const toSubscribe = [...this.testControllers.keys()];
setTimeout(() => {
for (const subscription of toSubscribe) {
this.testSubscriptions.get(subscription)?.subscribeFn(providerId, provider);
this.testControllers.get(subscription)?.subscribeFn(controllerId, controller);
}
}, 0);
return new Disposable(() => {
this.providers.delete(providerId);
this.proxy.$unregisterTestProvider(providerId);
this.controllers.delete(controllerId);
this.proxy.$unregisterTestController(controllerId);
});
}
@@ -88,8 +96,8 @@ export class ExtHostTesting implements ExtHostTestingShape {
/**
* Implements vscode.test.runTests
*/
public async runTests(req: vscode.TestRunOptions<vscode.TestItem>, token = CancellationToken.None) {
const testListToProviders = (tests: ReadonlyArray<vscode.TestItem>) =>
public async runTests(req: vscode.TestRunRequest<unknown>, token = CancellationToken.None) {
const testListToProviders = (tests: ReadonlyArray<vscode.TestItem<unknown>>) =>
tests
.map(this.getInternalTestForReference, this)
.filter(isDefined)
@@ -103,10 +111,10 @@ export class ExtHostTesting implements ExtHostTestingShape {
}
/**
* Implements vscode.test.publishTestResults
* Implements vscode.test.createTestRun
*/
public publishExtensionProvidedResults(results: vscode.TestResults, persist: boolean): void {
this.proxy.$publishExtensionProvidedResults(Convert.TestResults.from(generateUuid(), results), persist);
public createTestRun<T>(extensionId: string, request: vscode.TestRunRequest<T>, name: string | undefined, persist = true): vscode.TestRun<T> {
return this.runQueue.createTestRun(extensionId, request, name, persist);
}
/**
@@ -132,12 +140,12 @@ export class ExtHostTesting implements ExtHostTestingShape {
public async $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
const uri = URI.revive(uriComponents);
const subscriptionKey = getTestSubscriptionKey(resource, uri);
if (this.testSubscriptions.has(subscriptionKey)) {
if (this.testControllers.has(subscriptionKey)) {
return;
}
const cancellation = new CancellationTokenSource();
let method: undefined | ((p: vscode.TestProvider) => vscode.ProviderResult<vscode.TestItem>);
let method: undefined | ((p: vscode.TestController<unknown>) => vscode.ProviderResult<vscode.TestItem<unknown>>);
if (resource === ExtHostTestingResource.TextDocument) {
let document = this.documents.getDocument(uri);
@@ -156,14 +164,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
if (document) {
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
method = p => p.provideDocumentTestRoot
? p.provideDocumentTestRoot(document!.document, cancellation.token)
method = p => p.createDocumentTestRoot
? p.createDocumentTestRoot(document!.document, cancellation.token)
: createDefaultDocumentTestRoot(p, document!.document, folder, cancellation.token);
}
} else {
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
if (folder) {
method = p => p.provideWorkspaceTestRoot(folder, cancellation.token);
method = p => p.createWorkspaceTestRoot(folder, cancellation.token);
}
}
@@ -171,7 +179,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
return;
}
const subscribeFn = async (id: string, provider: vscode.TestProvider) => {
const subscribeFn = async (id: string, provider: vscode.TestController<unknown>) => {
try {
const root = await method!(provider);
if (root) {
@@ -186,15 +194,16 @@ export class ExtHostTesting implements ExtHostTestingShape {
const collection = disposable.add(this.ownedTests.createForHierarchy(
diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
disposable.add(toDisposable(() => cancellation.dispose(true)));
for (const [id, provider] of this.providers) {
subscribeFn(id, provider);
const subscribes: Promise<void>[] = [];
for (const [id, controller] of this.controllers) {
subscribes.push(subscribeFn(id, controller.instance));
}
// note: we don't increment the root count initially -- this is done by the
// note: we don't increment the count initially -- this is done by the
// main thread, incrementing once per extension host. We just push the
// diff to signal that roots have been discovered.
collection.pushDiff([TestDiffOpType.DeltaRootsComplete, -1]);
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn });
Promise.all(subscribes).then(() => collection.pushDiff([TestDiffOpType.IncrementPendingExtHosts, -1]));
this.testControllers.set(subscriptionKey, { store: disposable, collection, subscribeFn });
}
/**
@@ -203,7 +212,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
* @override
*/
public async $expandTest(test: TestIdWithSrc, levels: number) {
const sub = mapFind(this.testSubscriptions.values(), s => s.collection.treeId === test.src.tree ? s : undefined);
const sub = mapFind(this.testControllers.values(), s => s.collection.treeId === test.src.tree ? s : undefined);
await sub?.collection.expand(test.testId, levels < 0 ? Infinity : levels);
this.flushCollectionDiffs();
}
@@ -215,8 +224,8 @@ export class ExtHostTesting implements ExtHostTestingShape {
public $unsubscribeFromTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
const uri = URI.revive(uriComponents);
const subscriptionKey = getTestSubscriptionKey(resource, uri);
this.testSubscriptions.get(subscriptionKey)?.store.dispose();
this.testSubscriptions.delete(subscriptionKey);
this.testControllers.get(subscriptionKey)?.store.dispose();
this.testControllers.delete(subscriptionKey);
}
/**
@@ -237,14 +246,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
* providers to be run.
* @override
*/
public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise<void> {
const provider = this.providers.get(req.tests[0].src.provider);
if (!provider) {
public async $runTestsForProvider(req: RunTestForProviderRequest, token: CancellationToken): Promise<void> {
const controller = this.controllers.get(req.tests[0].src.controller);
if (!controller) {
return;
}
const includeTests = req.tests
.map(({ testId, src }) => this.ownedTests.getTestById(testId, src.tree))
.map(({ testId, src }) => this.ownedTests.getTestById(testId, src?.tree))
.filter(isDefined)
.map(([_tree, test]) => test);
@@ -259,35 +268,19 @@ export class ExtHostTesting implements ExtHostTestingShape {
return;
}
try {
await provider.runTests({
setState: (test, state) => {
// for test providers that don't support excluding natively,
// make sure not to report excluded result otherwise summaries will be off.
for (const [tree, exclude] of excludeTests) {
const e = tree.comparePositions(exclude, test.id);
if (e === TestPosition.IsChild || e === TestPosition.IsSame) {
return;
}
}
const publicReq: vscode.TestRunRequest<unknown> = {
tests: includeTests.map(t => TestItemFilteredWrapper.unwrap(t.actual)),
exclude: excludeTests.map(([, t]) => TestItemFilteredWrapper.unwrap(t.actual)),
debug: req.debug,
};
this.flushCollectionDiffs();
this.proxy.$updateTestStateInRun(req.runId, test.id, Convert.TestState.from(state));
},
tests: includeTests.map(t => TestItemFilteredWrapper.unwrap(t.actual)),
exclude: excludeTests.map(([, t]) => TestItemFilteredWrapper.unwrap(t.actual)),
debug: req.debug,
}, cancellation);
for (const { collection } of this.testSubscriptions.values()) {
collection.flushDiff(); // ensure all states are updated
}
return;
} catch (e) {
console.error(e); // so it appears to attached debuggers
throw e;
}
await this.runQueue.enqueueRun({
dto: TestRunDto.fromInternal(req),
token,
extensionId: controller.extensionId,
req: publicReq,
doRun: () => controller!.instance.runTests(publicReq, token)
});
}
public $lookupTest(req: TestIdWithSrc): Promise<InternalTestItem | undefined> {
@@ -305,7 +298,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
* main thread is updated.
*/
private flushCollectionDiffs() {
for (const { collection } of this.testSubscriptions.values()) {
for (const { collection } of this.testControllers.values()) {
collection.flushDiff();
}
}
@@ -313,18 +306,236 @@ export class ExtHostTesting implements ExtHostTestingShape {
/**
* Gets the internal test item associated with the reference from the extension.
*/
private getInternalTestForReference(test: vscode.TestItem) {
private getInternalTestForReference(test: vscode.TestItem<unknown>) {
// Find workspace items first, then owned tests, then document tests.
// If a test instance exists in both the workspace and document, prefer
// the workspace because it's less ephemeral.
return this.workspaceObservers.getMirroredTestDataByReference(test)
?? mapFind(this.testSubscriptions.values(), c => c.collection.getTestByReference(test))
?? mapFind(this.testControllers.values(), c => c.collection.getTestByReference(test))
?? this.textDocumentObservers.getMirroredTestDataByReference(test);
}
}
export const createDefaultDocumentTestRoot = async <T extends vscode.TestItem>(
provider: vscode.TestProvider<T>,
/**
* Queues runs for a single extension and provides the currently-executing
* run so that `createTestRun` can be properly correlated.
*/
class TestRunQueue {
private readonly state = new Map</* extensionId */ string, {
current: {
publicReq: vscode.TestRunRequest<unknown>,
factory: (name: string | undefined) => TestRunTask<unknown>,
},
queue: (() => (Promise<void> | void))[];
}>();
constructor(private readonly proxy: MainThreadTestingShape) { }
/**
* Registers and enqueues a test run. `doRun` will be called when an
* invokation to {@link TestController.runTests} should be called.
*/
public enqueueRun(opts: {
extensionId: string,
req: vscode.TestRunRequest<unknown>,
dto: TestRunDto,
token: CancellationToken,
doRun: () => Thenable<void> | void,
},
) {
let record = this.state.get(opts.extensionId);
if (!record) {
record = { queue: [], current: undefined as any };
this.state.set(opts.extensionId, record);
}
const deferred = new DeferredPromise<void>();
const runner = () => {
const tasks: TestRunTask<unknown>[] = [];
const shared = new Set<string>();
record!.current = {
publicReq: opts.req,
factory: name => {
const task = new TestRunTask(name, opts.dto, shared, this.proxy);
tasks.push(task);
opts.token.onCancellationRequested(() => task.end());
return task;
},
};
this.invokeRunner(opts.extensionId, opts.dto.id, opts.doRun, tasks).finally(() => deferred.complete());
};
record.queue.push(runner);
if (record.queue.length === 1) {
runner();
}
return deferred.p;
}
/**
* Implements the public `createTestRun` API.
*/
public createTestRun<T>(extensionId: string, request: vscode.TestRunRequest<T>, name: string | undefined, persist: boolean): vscode.TestRun<T> {
const state = this.state.get(extensionId);
// If the request is for the currently-executing `runTests`, then correlate
// it to that existing run. Otherwise return a new, detached run.
if (state?.current.publicReq === request) {
return state.current.factory(name);
}
const dto = TestRunDto.fromPublic(request);
const task = new TestRunTask(name, dto, new Set(), this.proxy);
this.proxy.$startedExtensionTestRun({
debug: request.debug,
exclude: request.exclude?.map(t => t.id) ?? [],
id: dto.id,
tests: request.tests.map(t => t.id),
persist: persist
});
task.onEnd.wait().then(() => this.proxy.$finishedExtensionTestRun(dto.id));
return task;
}
private invokeRunner<T>(extensionId: string, runId: string, fn: () => Thenable<void> | void, tasks: TestRunTask<T>[]): Promise<void> {
try {
const res = fn();
if (isThenable(res)) {
return res
.then(() => this.handleInvokeResult(extensionId, runId, tasks, undefined))
.catch(err => this.handleInvokeResult(extensionId, runId, tasks, err));
} else {
return this.handleInvokeResult(extensionId, runId, tasks, undefined);
}
} catch (e) {
return this.handleInvokeResult(extensionId, runId, tasks, e);
}
}
private async handleInvokeResult<T>(extensionId: string, runId: string, tasks: TestRunTask<T>[], error?: Error) {
const record = this.state.get(extensionId);
if (!record) {
return;
}
record.queue.shift();
if (record.queue.length > 0) {
record.queue[0]();
} else {
this.state.delete(extensionId);
}
await Promise.all(tasks.map(t => t.onEnd.wait()));
}
}
class TestRunDto {
public static fromPublic(request: vscode.TestRunRequest<unknown>) {
return new TestRunDto(
generateUuid(),
new Set(request.tests.map(t => t.id)),
new Set(request.exclude?.map(t => t.id) ?? Iterable.empty()),
);
}
public static fromInternal(request: RunTestForProviderRequest) {
return new TestRunDto(
request.runId,
new Set(request.tests.map(t => t.testId)),
new Set(request.excludeExtIds),
);
}
constructor(
public readonly id: string,
private readonly include: ReadonlySet<string>,
private readonly exclude: ReadonlySet<string>,
) { }
public isIncluded(test: vscode.TestItem<unknown>) {
for (let t: vscode.TestItem<unknown> | undefined = test; t; t = t.parent) {
if (this.include.has(t.id)) {
return true;
} else if (this.exclude.has(t.id)) {
return false;
}
}
return true;
}
}
class TestRunTask<T> implements vscode.TestRun<T> {
readonly #proxy: MainThreadTestingShape;
readonly #req: TestRunDto;
readonly #taskId = generateUuid();
readonly #sharedIds: Set<string>;
public readonly onEnd = new Barrier();
constructor(
public readonly name: string | undefined,
dto: TestRunDto,
sharedTestIds: Set<string>,
proxy: MainThreadTestingShape,
) {
this.#proxy = proxy;
this.#req = dto;
this.#sharedIds = sharedTestIds;
proxy.$startedTestRunTask(dto.id, { id: this.#taskId, name, running: true });
}
setState(test: vscode.TestItem<T>, state: vscode.TestResultState, duration?: number): void {
if (this.#req.isIncluded(test)) {
this.ensureTestIsKnown(test);
this.#proxy.$updateTestStateInRun(this.#req.id, this.#taskId, test.id, state, duration);
}
}
appendMessage(test: vscode.TestItem<T>, message: vscode.TestMessage): void {
if (this.#req.isIncluded(test)) {
this.ensureTestIsKnown(test);
this.#proxy.$appendTestMessageInRun(this.#req.id, this.#taskId, test.id, Convert.TestMessage.from(message));
}
}
appendOutput(output: string): void {
this.#proxy.$appendOutputToRun(this.#req.id, this.#taskId, VSBuffer.fromString(output));
}
end(): void {
this.#proxy.$finishedTestRunTask(this.#req.id, this.#taskId);
this.onEnd.open();
}
private ensureTestIsKnown(test: vscode.TestItem<T>) {
const sent = this.#sharedIds;
if (sent.has(test.id)) {
return;
}
const chain: ITestItem[] = [];
while (true) {
chain.unshift(Convert.TestItem.from(test));
if (sent.has(test.id)) {
break;
}
sent.add(test.id);
if (!test.parent) {
break;
}
test = test.parent;
}
this.#proxy.$addTestsToRun(this.#req.id, chain);
}
}
export const createDefaultDocumentTestRoot = async <T>(
provider: vscode.TestController<T>,
document: vscode.TextDocument,
folder: vscode.WorkspaceFolder | undefined,
token: CancellationToken,
@@ -333,7 +544,7 @@ export const createDefaultDocumentTestRoot = async <T extends vscode.TestItem>(
return;
}
const root = await provider.provideWorkspaceTestRoot(folder, token);
const root = await provider.createWorkspaceTestRoot(folder, token);
if (!root) {
return;
}
@@ -342,32 +553,35 @@ export const createDefaultDocumentTestRoot = async <T extends vscode.TestItem>(
TestItemFilteredWrapper.removeFilter(document);
});
return TestItemFilteredWrapper.getWrapperForTestItem(root, document);
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(root, document);
wrapper.refreshMatch();
return wrapper;
};
/*
* A class which wraps a vscode.TestItem that provides the ability to filter a TestItem's children
* to only the children that are located in a certain vscode.Uri.
*/
export class TestItemFilteredWrapper<T extends vscode.TestItem = vscode.TestItem> extends TestItemImpl {
private static wrapperMap = new WeakMap<vscode.TextDocument, WeakMap<vscode.TestItem, TestItemFilteredWrapper>>();
export class TestItemFilteredWrapper extends TestItemImpl {
private static wrapperMap = new WeakMap<vscode.TextDocument, WeakMap<vscode.TestItem<unknown>, TestItemFilteredWrapper>>();
public static removeFilter(document: vscode.TextDocument): void {
this.wrapperMap.delete(document);
}
// Wraps the TestItem specified in a TestItemFilteredWrapper and pulls from a cache if it already exists.
public static getWrapperForTestItem<T extends vscode.TestItem>(
item: T,
public static getWrapperForTestItem(
item: vscode.TestItem<unknown>,
filterDocument: vscode.TextDocument,
parent?: TestItemFilteredWrapper<T>,
): TestItemFilteredWrapper<T> {
parent?: TestItemFilteredWrapper,
): TestItemFilteredWrapper {
let innerMap = this.wrapperMap.get(filterDocument);
if (innerMap?.has(item)) {
return innerMap.get(item) as TestItemFilteredWrapper<T>;
return innerMap.get(item) as TestItemFilteredWrapper;
}
if (!innerMap) {
innerMap = new WeakMap<vscode.TestItem, TestItemFilteredWrapper>();
innerMap = new WeakMap();
this.wrapperMap.set(filterDocument, innerMap);
}
@@ -380,8 +594,8 @@ export class TestItemFilteredWrapper<T extends vscode.TestItem = vscode.TestItem
* If the TestItem is wrapped, returns the unwrapped item provided
* by the extension.
*/
public static unwrap(item: vscode.TestItem) {
return item instanceof TestItemFilteredWrapper ? item.actual : item;
public static unwrap<T>(item: vscode.TestItem<T> | TestItemFilteredWrapper) {
return item instanceof TestItemFilteredWrapper ? item.actual as vscode.TestItem<T> : item;
}
private _cachedMatchesFilter: boolean | undefined;
@@ -398,21 +612,39 @@ export class TestItemFilteredWrapper<T extends vscode.TestItem = vscode.TestItem
}
private constructor(
public readonly actual: T,
public readonly actual: vscode.TestItem<unknown>,
private filterDocument: vscode.TextDocument,
public readonly parent?: TestItemFilteredWrapper<T>,
public readonly actualParent?: TestItemFilteredWrapper,
) {
super(actual.id, actual.label, actual.uri, actual.expandable);
super(actual.id, actual.label, actual.uri, undefined);
if (!(actual instanceof TestItemImpl)) {
throw new Error(`TestItems provided to the VS Code API must extend \`vscode.TestItem\`, but ${actual.id} did not`);
}
(actual as TestItemImpl)[TestItemHookProperty] = {
setProp: (key, value) => (this as Record<string, unknown>)[key] = value,
created: child => TestItemFilteredWrapper.getWrapperForTestItem(child, this.filterDocument, this).refreshMatch(),
invalidate: () => this.invalidate(),
delete: child => this.children.delete(child),
};
this.debuggable = actual.debuggable;
this.runnable = actual.runnable;
this.description = actual.description;
this.error = actual.error;
this.status = actual.status;
this.range = actual.range;
this.resolveHandler = actual.resolveHandler;
const wrapperApi = getPrivateApiFor(this);
const actualApi = getPrivateApiFor(actual);
actualApi.bus.event(evt => {
switch (evt[0]) {
case ExtHostTestItemEventType.SetProp:
(this as Record<string, unknown>)[evt[1]] = evt[2];
break;
case ExtHostTestItemEventType.NewChild:
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(evt[1], this.filterDocument, this);
getPrivateApiFor(wrapper).parent = actual;
wrapper.refreshMatch();
break;
default:
wrapperApi.bus.fire(evt);
}
});
}
/**
@@ -420,17 +652,17 @@ export class TestItemFilteredWrapper<T extends vscode.TestItem = vscode.TestItem
* if the test itself has a location that matches, or if any of its
* children do.
*/
private refreshMatch() {
public refreshMatch() {
const didMatch = this._cachedMatchesFilter;
// The `children` of the wrapper only include the children who match the
// filter. Synchronize them.
for (const rawChild of this.actual.children) {
for (const rawChild of this.actual.children.values()) {
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(rawChild, this.filterDocument, this);
if (wrapper.hasNodeMatchingFilter) {
this.children.add(wrapper);
} else {
this.children.delete(wrapper);
if (!wrapper.hasNodeMatchingFilter) {
wrapper.dispose();
} else if (!this.children.has(wrapper.id)) {
this.addChild(wrapper);
}
}
@@ -438,18 +670,26 @@ export class TestItemFilteredWrapper<T extends vscode.TestItem = vscode.TestItem
this._cachedMatchesFilter = nowMatches;
if (nowMatches !== didMatch) {
this.parent?.refreshMatch();
this.actualParent?.refreshMatch();
}
return this._cachedMatchesFilter;
}
public override dispose() {
if (this.actualParent) {
getPrivateApiFor(this.actualParent).children.delete(this.id);
}
getPrivateApiFor(this).bus.fire([ExtHostTestItemEventType.Disposed]);
}
}
/**
* @private
*/
interface MirroredCollectionTestItem extends IncrementalTestCollectionItem {
revived: vscode.TestItem;
revived: vscode.TestItem<never>;
depth: number;
}
@@ -464,21 +704,21 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
return this.added.size === 0 && this.removed.size === 0 && this.updated.size === 0;
}
constructor(private readonly emitter: Emitter<vscode.TestChangeEvent>) {
constructor(private readonly emitter: Emitter<vscode.TestsChangeEvent>) {
super();
}
/**
* @override
*/
public add(node: MirroredCollectionTestItem): void {
public override add(node: MirroredCollectionTestItem): void {
this.added.add(node);
}
/**
* @override
*/
public update(node: MirroredCollectionTestItem): void {
public override update(node: MirroredCollectionTestItem): void {
Object.assign(node.revived, Convert.TestItem.toPlain(node.item));
if (!this.added.has(node)) {
this.updated.add(node);
@@ -488,7 +728,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
/**
* @override
*/
public remove(node: MirroredCollectionTestItem): void {
public override remove(node: MirroredCollectionTestItem): void {
if (this.added.has(node)) {
this.added.delete(node);
return;
@@ -507,7 +747,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
/**
* @override
*/
public getChangeEvent(): vscode.TestChangeEvent {
public getChangeEvent(): vscode.TestsChangeEvent {
const { added, updated, removed } = this;
return {
get added() { return [...added].map(n => n.revived); },
@@ -516,7 +756,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
};
}
public complete() {
public override complete() {
if (!this.isEmpty) {
this.emitter.fire(this.getChangeEvent());
}
@@ -528,7 +768,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
* @private
*/
export class MirroredTestCollection extends AbstractIncrementalTestCollection<MirroredCollectionTestItem> {
private changeEmitter = new Emitter<vscode.TestChangeEvent>();
private changeEmitter = new Emitter<vscode.TestsChangeEvent>();
/**
* Change emitter that fires with the same sematics as `TestObserver.onDidChangeTests`.
@@ -546,7 +786,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
* Translates the item IDs to TestItems for exposure to extensions.
*/
public getAllAsTestItem(itemIds: Iterable<string>) {
let output: vscode.TestItem[] = [];
let output: vscode.TestItem<never>[] = [];
for (const itemId of itemIds) {
const item = this.items.get(itemId);
if (item) {
@@ -568,7 +808,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
/**
* If the test item is a mirrored test item, returns its underlying ID.
*/
public getMirroredTestDataByReference(item: vscode.TestItem) {
public getMirroredTestDataByReference(item: vscode.TestItem<unknown>) {
return this.items.get(item.id);
}
@@ -579,7 +819,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
return {
...item,
// todo@connor4312: make this work well again with children
revived: Convert.TestItem.toPlain(item.item) as vscode.TestItem,
revived: Convert.TestItem.toPlain(item.item) as vscode.TestItem<never>,
depth: parent ? parent.depth + 1 : 0,
children: new Set(),
};
@@ -588,7 +828,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
/**
* @override
*/
protected createChangeCollector() {
protected override createChangeCollector() {
return new MirroredChangeCollector(this.changeEmitter);
}
}
@@ -628,7 +868,7 @@ abstract class AbstractTestObserverFactory {
/**
* Gets the internal test data by its reference, in any observer.
*/
public getMirroredTestDataByReference(ref: vscode.TestItem) {
public getMirroredTestDataByReference(ref: vscode.TestItem<unknown>) {
for (const { tests } of this.resources.values()) {
const v = tests.getMirroredTestDataByReference(ref);
if (v) {

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { TestItemImpl } from 'vs/workbench/api/common/extHostTypes';
import * as vscode from 'vscode';
export const enum ExtHostTestItemEventType {
NewChild,
Disposed,
Invalidated,
SetProp,
}
export type ExtHostTestItemEvent =
| [evt: ExtHostTestItemEventType.NewChild, item: TestItemImpl]
| [evt: ExtHostTestItemEventType.Disposed]
| [evt: ExtHostTestItemEventType.Invalidated]
| [evt: ExtHostTestItemEventType.SetProp, key: keyof vscode.TestItem<never>, value: any];
export interface IExtHostTestItemApi {
children: Map<string, TestItemImpl>;
parent?: TestItemImpl;
bus: Emitter<ExtHostTestItemEvent>;
}
const eventPrivateApis = new WeakMap<TestItemImpl, IExtHostTestItemApi>();
/**
* Gets the private API for a test item implementation. This implementation
* is a managed object, but we keep a weakmap to avoid exposing any of the
* internals to extensions.
*/
export const getPrivateApiFor = (impl: TestItemImpl) => {
let api = eventPrivateApis.get(impl);
if (!api) {
api = { children: new Map(), bus: new Emitter() };
eventPrivateApis.set(impl, api);
}
return api;
};

View File

@@ -497,12 +497,12 @@ class ExtHostTreeView<T> extends Disposable {
private getHandlesToRefresh(elements: T[]): TreeItemHandle[] {
const elementsToUpdate = new Set<TreeItemHandle>();
for (const element of elements) {
const elementNode = this.nodes.get(element);
const elementNodes = elements.map(element => this.nodes.get(element));
for (const elementNode of elementNodes) {
if (elementNode && !elementsToUpdate.has(elementNode.item.handle)) {
// check if an ancestor of extElement is already in the elements to update list
// check if an ancestor of extElement is already in the elements list
let currentNode: TreeNode | undefined = elementNode;
while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent.item.handle)) {
while (currentNode && currentNode.parent && elementNodes.findIndex(node => currentNode && currentNode.parent && node && node.item.handle === currentNode.parent.item.handle) === -1) {
const parentElement: T | undefined = this.elements.get(currentNode.parent.item.handle);
currentNode = parentElement ? this.nodes.get(parentElement) : undefined;
}
@@ -749,7 +749,7 @@ class ExtHostTreeView<T> extends Disposable {
this.nodes.clear();
}
dispose() {
override dispose() {
this._refreshCancellationSource.dispose();
this.clearAll();

View File

@@ -46,7 +46,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
getTunnels(): Promise<vscode.TunnelDescription[]>;
onDidChangeTunnels: vscode.Event<void>;
setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider): IDisposable;
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number], commandMatcher?: RegExp }, provider: vscode.PortAttributesProvider): IDisposable;
}
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');

View File

@@ -5,6 +5,7 @@
import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
import * as htmlContent from 'vs/base/common/htmlContent';
import { DisposableStore } from 'vs/base/common/lifecycle';
import * as marked from 'vs/base/common/marked/marked';
import { parse } from 'vs/base/common/marshalling';
import { cloneAndChange } from 'vs/base/common/objects';
@@ -27,8 +28,9 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocum
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import * as search from 'vs/workbench/contrib/search/common/search';
import { ISerializedTestResults, ITestItem, ITestMessage, ITestState, SerializedTestResultItem, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection';
import { ISerializedTestResults, ITestItem, ITestMessage, SerializedTestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import type * as vscode from 'vscode';
import * as types from './extHostTypes';
@@ -543,7 +545,7 @@ export namespace WorkspaceEdit {
resource: entry.uri,
edit: entry.edit,
notebookMetadata: entry.notebookMetadata,
notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version
notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.apiNotebook.version
});
} else if (entry._type === types.FileEditType.CellOutput) {
@@ -578,7 +580,7 @@ export namespace WorkspaceEdit {
_type: extHostProtocol.WorkspaceEditType.Cell,
metadata: entry.metadata,
resource: entry.uri,
notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version,
notebookVersionId: extHostNotebooks?.lookupNotebookDocument(entry.uri)?.apiNotebook.version,
edit: {
editType: notebooks.CellEditType.Replace,
index: entry.index,
@@ -1404,21 +1406,31 @@ export namespace LanguageSelector {
}
}
export namespace NotebookCellRange {
export namespace NotebookRange {
export function from(range: vscode.NotebookCellRange): notebooks.ICellRange {
export function from(range: vscode.NotebookRange): ICellRange {
return { start: range.start, end: range.end };
}
export function to(range: notebooks.ICellRange): types.NotebookCellRange {
return new types.NotebookCellRange(range.start, range.end);
export function to(range: ICellRange): types.NotebookRange {
return new types.NotebookRange(range.start, range.end);
}
}
export namespace NotebookCellMetadata {
export function to(data: notebooks.NotebookCellMetadata): types.NotebookCellMetadata {
return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.hasExecutionOrder, data.statusMessage, data.inputCollapsed, data.outputCollapsed, data.custom);
return new types.NotebookCellMetadata().with({
...data,
...{
executionOrder: null,
lastRunSuccess: null,
runState: null,
runStartTime: null,
runStartTimeAdjustment: null,
runEndTime: null
}
});
}
}
@@ -1429,14 +1441,15 @@ export namespace NotebookDocumentMetadata {
}
export function to(data: notebooks.NotebookDocumentMetadata): types.NotebookDocumentMetadata {
return new types.NotebookDocumentMetadata(data.editable, data.cellEditable, data.cellHasExecutionOrder, data.custom, data.trusted);
return new types.NotebookDocumentMetadata().with(data);
}
}
export namespace NotebookCellPreviousExecutionResult {
export function to(data: notebooks.NotebookCellMetadata): vscode.NotebookCellExecutionSummary {
return {
duration: data.lastRunDuration,
startTime: data.runStartTime,
endTime: data.runEndTime,
executionOrder: data.executionOrder,
success: data.lastRunSuccess
};
@@ -1445,7 +1458,8 @@ export namespace NotebookCellPreviousExecutionResult {
export function from(data: vscode.NotebookCellExecutionSummary): Partial<notebooks.NotebookCellMetadata> {
return {
lastRunSuccess: data.success,
lastRunDuration: data.duration,
runStartTime: data.startTime,
runEndTime: data.endTime,
executionOrder: data.executionOrder
};
}
@@ -1612,38 +1626,52 @@ export namespace NotebookDecorationRenderOptions {
}
}
export namespace NotebookDocumentContentOptions {
export function from(options: vscode.NotebookDocumentContentOptions | undefined): notebooks.TransientOptions {
export namespace NotebookStatusBarItem {
export function from(item: vscode.NotebookCellStatusBarItem, commandsConverter: CommandsConverter, disposables: DisposableStore): notebooks.INotebookCellStatusBarItem {
const command = typeof item.command === 'string' ? { title: '', command: item.command } : item.command;
return {
transientOutputs: options ? options.transientOutputs : false,
transientMetadata: {
...(options?.transientMetadata ?? {}),
...{
executionOrder: true,
lastRunDuration: true,
runState: true,
runStartTime: true,
lastRunSuccess: true
}
}
alignment: item.alignment === types.NotebookCellStatusBarAlignment.Left ? notebooks.CellStatusbarAlignment.Left : notebooks.CellStatusbarAlignment.Right,
command: commandsConverter.toInternal(command, disposables), // TODO@roblou
text: item.text,
tooltip: item.tooltip,
accessibilityInformation: item.accessibilityInformation,
priority: item.priority
};
}
}
export namespace TestState {
export function from(item: vscode.TestState): ITestState {
export namespace NotebookDocumentContentOptions {
export function from(options: vscode.NotebookDocumentContentOptions | undefined): notebooks.TransientOptions {
return {
state: item.state,
duration: item.duration,
messages: item.messages.map(TestMessage.from),
transientOutputs: options?.transientOutputs ?? false,
transientCellMetadata: {
...options?.transientCellMetadata,
executionOrder: true,
runState: true,
runStartTime: true,
runStartTimeAdjustment: true,
runEndTime: true,
lastRunSuccess: true
},
transientDocumentMetadata: options?.transientDocumentMetadata ?? {}
};
}
}
export function to(item: ITestState): vscode.TestState {
const ts = new types.TestState(item.state);
ts.duration = item.duration;
ts.messages = item.messages.map(TestMessage.to);
return ts;
export namespace NotebookKernelPreload {
export function from(preload: vscode.NotebookKernelPreload): { uri: UriComponents; provides: string[] } {
return {
uri: preload.uri,
provides: typeof preload.provides === 'string'
? [preload.provides]
: preload.provides ?? []
};
}
export function to(preload: { uri: UriComponents; provides: string[] }): vscode.NotebookKernelPreload {
return {
uri: URI.revive(preload.uri),
provides: preload.provides
};
}
}
@@ -1668,18 +1696,18 @@ export namespace TestMessage {
}
export namespace TestItem {
export type Raw = vscode.TestItem;
export type Raw<T = unknown> = vscode.TestItem<T>;
export function from(item: vscode.TestItem): ITestItem {
export function from(item: vscode.TestItem<unknown>): ITestItem {
return {
extId: item.id,
label: item.label,
uri: item.uri,
range: Range.from(item.range),
range: Range.from(item.range) || null,
debuggable: item.debuggable ?? false,
description: item.description,
description: item.description || null,
runnable: item.runnable ?? true,
expandable: item.expandable,
error: item.error ? (MarkdownString.fromStrict(item.error) || null) : null,
};
}
@@ -1688,84 +1716,55 @@ export namespace TestItem {
extId: item.id,
label: item.label,
uri: item.uri,
range: Range.from(item.range),
range: Range.from(item.range) || null,
debuggable: false,
description: item.description,
description: item.description || null,
error: null,
runnable: true,
expandable: true,
};
}
export function toPlain(item: ITestItem): Omit<vscode.TestItem, 'children' | 'invalidate' | 'discoverChildren'> {
export function toPlain(item: ITestItem): Omit<vscode.TestItem<never>, 'children' | 'invalidate' | 'discoverChildren'> {
return {
id: item.extId,
label: item.label,
uri: URI.revive(item.uri),
range: Range.to(item.range),
expandable: item.expandable,
range: Range.to(item.range || undefined),
addChild: () => undefined,
dispose: () => undefined,
status: types.TestItemStatus.Pending,
data: undefined as never,
debuggable: item.debuggable,
description: item.description,
description: item.description || undefined,
runnable: item.runnable,
};
}
export function to(item: ITestItem): types.TestItem {
const testItem = new types.TestItem(item.extId, item.label, URI.revive(item.uri), item.expandable);
testItem.range = Range.to(item.range);
export function to(item: ITestItem): types.TestItemImpl {
const testItem = new types.TestItemImpl(item.extId, item.label, URI.revive(item.uri), undefined);
testItem.range = Range.to(item.range || undefined);
testItem.debuggable = item.debuggable;
testItem.description = item.description;
testItem.description = item.description || undefined;
testItem.runnable = item.runnable;
return testItem;
}
}
export namespace TestResults {
export function from(id: string, results: vscode.TestResults): ISerializedTestResults {
const serialized: ISerializedTestResults = {
completedAt: results.completedAt,
id,
items: [],
};
const queue: [parent: SerializedTestResultItem | null, children: Iterable<vscode.TestResultSnapshot>][] = [
[null, results.results],
];
while (queue.length) {
const [parent, children] = queue.pop()!;
for (const item of children) {
const serializedItem: SerializedTestResultItem = {
children: item.children?.map(c => c.id) ?? [],
computedState: item.result.state,
item: TestItem.fromResultSnapshot(item),
state: TestState.from(item.result),
retired: undefined,
expand: TestItemExpandState.Expanded,
parent: parent?.item.extId ?? null,
src: { provider: '', tree: -1 },
direct: !parent,
};
serialized.items.push(serializedItem);
if (item.children) {
queue.push([serializedItem, item.children]);
}
}
}
return serialized;
}
const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map<string, SerializedTestResultItem>): vscode.TestResultSnapshot => ({
...TestItem.toPlain(item.item),
result: TestState.to(item.state),
taskStates: item.tasks.map(t => ({
state: t.state,
duration: t.duration,
messages: t.messages.map(TestMessage.to),
})),
children: item.children
.map(c => byInternalId.get(c))
.filter(isDefined)
.map(c => convertTestResultItem(c, byInternalId)),
});
export function to(serialized: ISerializedTestResults): vscode.TestResults {
export function to(serialized: ISerializedTestResults): vscode.TestRunResult {
const roots: SerializedTestResultItem[] = [];
const byInternalId = new Map<string, SerializedTestResultItem>();
for (const item of serialized.items) {

View File

@@ -7,12 +7,14 @@ import { coalesceInPlace, equals } from 'vs/base/common/arrays';
import { illegalArgument } from 'vs/base/common/errors';
import { IRelativePattern } from 'vs/base/common/glob';
import { isMarkdownString, MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent';
import { ResourceMap } from 'vs/base/common/map';
import { ReadonlyMapView, ResourceMap } from 'vs/base/common/map';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { isStringArray } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getPrivateApiFor, ExtHostTestItemEventType, IExtHostTestItemApi } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import { CellEditType, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as vscode from 'vscode';
@@ -419,7 +421,7 @@ export class Selection extends Range {
return this._anchor === this._end;
}
toJSON() {
override toJSON() {
return {
start: this.start,
end: this.end,
@@ -676,34 +678,33 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: value }, notebookMetadata: value });
}
replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
if (start !== end || cells.length > 0) {
this._edits.push({ _type: FileEditType.CellReplace, uri, index: start, count: end - start, cells, metadata });
replaceNotebookCells(uri: URI, range: vscode.NotebookRange, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void;
replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void;
replaceNotebookCells(uri: URI, startOrRange: number | vscode.NotebookRange, endOrCells: number | vscode.NotebookCellData[], cellsOrMetadata?: vscode.NotebookCellData[] | vscode.WorkspaceEditEntryMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
let start: number | undefined;
let end: number | undefined;
let cellData: vscode.NotebookCellData[] = [];
let workspaceEditMetadata: vscode.WorkspaceEditEntryMetadata | undefined;
if (NotebookRange.isNotebookRange(startOrRange) && NotebookCellData.isNotebookCellDataArray(endOrCells) && !NotebookCellData.isNotebookCellDataArray(cellsOrMetadata)) {
start = startOrRange.start;
end = startOrRange.end;
cellData = endOrCells;
workspaceEditMetadata = cellsOrMetadata;
} else if (typeof startOrRange === 'number' && typeof endOrCells === 'number' && NotebookCellData.isNotebookCellDataArray(cellsOrMetadata)) {
start = startOrRange;
end = endOrCells;
cellData = cellsOrMetadata;
workspaceEditMetadata = metadata;
}
}
replaceNotebookCellOutput(uri: URI, index: number, outputs: vscode.NotebookCellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._editNotebookCellOutput(uri, index, false, outputs, metadata);
}
if (start === undefined || end === undefined) {
throw new Error('Invalid arguments');
}
appendNotebookCellOutput(uri: URI, index: number, outputs: vscode.NotebookCellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._editNotebookCellOutput(uri, index, true, outputs, metadata);
}
replaceNotebookCellOutputItems(uri: URI, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._editNotebookCellOutputItems(uri, index, outputId, false, items, metadata);
}
appendNotebookCellOutputItems(uri: URI, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._editNotebookCellOutputItems(uri, index, outputId, true, items, metadata);
}
private _editNotebookCellOutputItems(uri: URI, index: number, id: string, append: boolean, items: vscode.NotebookCellOutputItem[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void {
this._edits.push({ _type: FileEditType.CellOutputItem, metadata, uri, index, outputId: id, append, newOutputItems: items });
}
private _editNotebookCellOutput(uri: URI, index: number, append: boolean, outputs: vscode.NotebookCellOutput[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void {
this._edits.push({ _type: FileEditType.CellOutput, metadata, uri, index, append, newOutputs: outputs, newMetadata: undefined });
if (start !== end || cellData.length > 0) {
this._edits.push({ _type: FileEditType.CellReplace, uri, index: start, count: end - start, cells: cellData, metadata: workspaceEditMetadata });
}
}
replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
@@ -2895,7 +2896,17 @@ export enum ColorThemeKind {
//#region Notebook
export class NotebookCellRange {
export class NotebookRange {
static isNotebookRange(thing: any): thing is vscode.NotebookRange {
if (thing instanceof NotebookRange) {
return true;
}
if (!thing) {
return false;
}
return typeof (<NotebookRange>thing).start === 'number'
&& typeof (<NotebookRange>thing).end === 'number';
}
private _start: number;
private _end: number;
@@ -2916,58 +2927,59 @@ export class NotebookCellRange {
if (start < 0) {
throw illegalArgument('start must be positive');
}
if (end < start) {
throw illegalArgument('end cannot be smaller than start');
if (end < 0) {
throw illegalArgument('end must be positive');
}
this._start = start;
this._end = end;
if (start <= end) {
this._start = start;
this._end = end;
} else {
this._start = end;
this._end = start;
}
}
with(change: { start?: number, end?: number }): NotebookRange {
let start = this._start;
let end = this._end;
if (change.start !== undefined) {
start = change.start;
}
if (change.end !== undefined) {
end = change.end;
}
if (start === this._start && end === this._end) {
return this;
}
return new NotebookRange(start, end);
}
}
export class NotebookCellMetadata {
readonly inputCollapsed?: boolean;
readonly outputCollapsed?: boolean;
readonly [key: string]: any;
constructor(
readonly editable?: boolean,
readonly breakpointMargin?: boolean,
readonly hasExecutionOrder?: boolean,
readonly statusMessage?: string,
readonly inputCollapsed?: boolean,
readonly outputCollapsed?: boolean,
readonly custom?: Record<string, any>,
) { }
constructor(inputCollapsed?: boolean, outputCollapsed?: boolean);
constructor(data: Record<string, any>);
constructor(inputCollapsedOrData: (boolean | undefined) | Record<string, any>, outputCollapsed?: boolean) {
if (typeof inputCollapsedOrData === 'object') {
Object.assign(this, inputCollapsedOrData);
} else {
this.inputCollapsed = inputCollapsedOrData;
this.outputCollapsed = outputCollapsed;
}
}
with(change: {
editable?: boolean | null,
breakpointMargin?: boolean | null,
hasExecutionOrder?: boolean | null,
statusMessage?: string | null,
inputCollapsed?: boolean | null,
outputCollapsed?: boolean | null,
custom?: Record<string, any> | null,
[key: string]: any
}): NotebookCellMetadata {
let { editable, breakpointMargin, hasExecutionOrder, statusMessage, inputCollapsed, outputCollapsed, custom } = change;
let { inputCollapsed, outputCollapsed, ...remaining } = change;
if (editable === undefined) {
editable = this.editable;
} else if (editable === null) {
editable = undefined;
}
if (breakpointMargin === undefined) {
breakpointMargin = this.breakpointMargin;
} else if (breakpointMargin === null) {
breakpointMargin = undefined;
}
if (hasExecutionOrder === undefined) {
hasExecutionOrder = this.hasExecutionOrder;
} else if (hasExecutionOrder === null) {
hasExecutionOrder = undefined;
}
if (statusMessage === undefined) {
statusMessage = this.statusMessage;
} else if (statusMessage === null) {
statusMessage = undefined;
}
if (inputCollapsed === undefined) {
inputCollapsed = this.inputCollapsed;
} else if (inputCollapsed === null) {
@@ -2978,103 +2990,78 @@ export class NotebookCellMetadata {
} else if (outputCollapsed === null) {
outputCollapsed = undefined;
}
if (custom === undefined) {
custom = this.custom;
} else if (custom === null) {
custom = undefined;
}
if (editable === this.editable &&
breakpointMargin === this.breakpointMargin &&
hasExecutionOrder === this.hasExecutionOrder &&
statusMessage === this.statusMessage &&
inputCollapsed === this.inputCollapsed &&
if (inputCollapsed === this.inputCollapsed &&
outputCollapsed === this.outputCollapsed &&
custom === this.custom
Object.keys(remaining).length === 0
) {
return this;
}
return new NotebookCellMetadata(
editable,
breakpointMargin,
hasExecutionOrder,
statusMessage,
inputCollapsed,
outputCollapsed,
custom,
{
inputCollapsed,
outputCollapsed,
...remaining
}
);
}
}
export class NotebookDocumentMetadata {
readonly trusted: boolean;
readonly [key: string]: any;
constructor(
readonly editable: boolean = true,
readonly cellEditable: boolean = true,
readonly cellHasExecutionOrder: boolean = true,
readonly custom: { [key: string]: any; } = {},
readonly trusted: boolean = true,
) { }
constructor(trusted?: boolean);
constructor(data: Record<string, any>);
constructor(trustedOrData: boolean | Record<string, any> = true) {
if (typeof trustedOrData === 'object') {
Object.assign(this, trustedOrData);
this.trusted = trustedOrData.trusted ?? true;
} else {
this.trusted = trustedOrData;
}
}
with(change: {
editable?: boolean | null,
cellEditable?: boolean | null,
cellHasExecutionOrder?: boolean | null,
custom?: { [key: string]: any; } | null,
trusted?: boolean | null,
[key: string]: any
}): NotebookDocumentMetadata {
let { editable, cellEditable, cellHasExecutionOrder, custom, trusted } = change;
let { trusted, ...remaining } = change;
if (editable === undefined) {
editable = this.editable;
} else if (editable === null) {
editable = undefined;
}
if (cellEditable === undefined) {
cellEditable = this.cellEditable;
} else if (cellEditable === null) {
cellEditable = undefined;
}
if (cellHasExecutionOrder === undefined) {
cellHasExecutionOrder = this.cellHasExecutionOrder;
} else if (cellHasExecutionOrder === null) {
cellHasExecutionOrder = undefined;
}
if (custom === undefined) {
custom = this.custom;
} else if (custom === null) {
custom = undefined;
}
if (trusted === undefined) {
trusted = this.trusted;
} else if (trusted === null) {
trusted = undefined;
}
if (editable === this.editable &&
cellEditable === this.cellEditable &&
cellHasExecutionOrder === this.cellHasExecutionOrder &&
custom === this.custom &&
trusted === this.trusted
if (trusted === this.trusted &&
Object.keys(remaining).length === 0
) {
return this;
}
return new NotebookDocumentMetadata(
editable,
cellEditable,
cellHasExecutionOrder,
custom,
trusted
{
trusted,
...remaining
}
);
}
}
export class NotebookCellData {
static isNotebookCellDataArray(value: unknown): value is vscode.NotebookCellData[] {
return Array.isArray(value) && (<unknown[]>value).every(elem => NotebookCellData.isNotebookCellData(elem));
}
static isNotebookCellData(value: unknown): value is vscode.NotebookCellData {
// return value instanceof NotebookCellData;
return true;
}
kind: NotebookCellKind;
source: string;
language: string;
@@ -3111,17 +3098,21 @@ export class NotebookCellOutputItem {
}
constructor(
readonly mime: string,
readonly value: unknown, // JSON'able
readonly metadata?: Record<string, any>
) { }
public mime: string,
public value: unknown, // JSON'able
public metadata?: Record<string, any>
) {
if (isFalsyOrWhitespace(this.mime)) {
throw new Error('INVALID mime type, must not be empty or falsy');
}
}
}
export class NotebookCellOutput {
readonly outputs: NotebookCellOutputItem[];
readonly id: string;
readonly metadata?: Record<string, any>;
id: string;
outputs: NotebookCellOutputItem[];
metadata?: Record<string, any>;
constructor(
outputs: NotebookCellOutputItem[],
@@ -3162,6 +3153,21 @@ export enum NotebookEditorRevealType {
AtTop = 3
}
export class NotebookCellStatusBarItem {
constructor(
public text: string,
public alignment: NotebookCellStatusBarAlignment,
public command?: string | vscode.Command,
public tooltip?: string,
public priority?: number,
public accessibilityInformation?: vscode.AccessibilityInformation) { }
}
export enum NotebookControllerAffinity {
Default = 1,
Preferred = 2
}
//#endregion
@@ -3223,7 +3229,7 @@ export class LinkedEditingRanges {
}
//#region Testing
export enum TestResult {
export enum TestResultState {
Unset = 0,
Queued = 1,
Running = 2,
@@ -3240,16 +3246,17 @@ export enum TestMessageSeverity {
Hint = 3
}
export const TestItemHookProperty = Symbol('TestItemHookProperty');
export interface ITestItemHook {
created(item: vscode.TestItem): void;
setProp<K extends keyof vscode.TestItem>(key: K, value: vscode.TestItem[K]): void;
invalidate(id: string): void;
delete(id: string): void;
export enum TestItemStatus {
Pending = 0,
Resolved = 1,
}
const testItemPropAccessor = <K extends keyof vscode.TestItem>(item: TestItem, key: K, defaultValue: vscode.TestItem[K]) => {
const testItemPropAccessor = <K extends keyof vscode.TestItem<never>>(
api: IExtHostTestItemApi,
key: K,
defaultValue: vscode.TestItem<never>[K],
equals: (a: vscode.TestItem<never>[K], b: vscode.TestItem<never>[K]) => boolean
) => {
let value = defaultValue;
return {
enumerable: true,
@@ -3257,80 +3264,41 @@ const testItemPropAccessor = <K extends keyof vscode.TestItem>(item: TestItem, k
get() {
return value;
},
set(newValue: vscode.TestItem[K]) {
item[TestItemHookProperty]?.setProp(key, newValue);
value = newValue;
set(newValue: vscode.TestItem<never>[K]) {
if (!equals(value, newValue)) {
value = newValue;
api.bus.fire([ExtHostTestItemEventType.SetProp, key, newValue]);
}
},
};
};
export class TestChildrenCollection implements vscode.TestChildrenCollection<vscode.TestItem> {
#map = new Map<string, vscode.TestItem>();
#hookRef: () => ITestItemHook | undefined;
const strictEqualComparator = <T>(a: T, b: T) => a === b;
const rangeComparator = (a: vscode.Range | undefined, b: vscode.Range | undefined) => {
if (a === b) { return true; }
if (!a || !b) { return false; }
return a.isEqual(b);
};
public get size() {
return this.#map.size;
}
export class TestItemImpl implements vscode.TestItem<unknown> {
public readonly id!: string;
public readonly uri!: vscode.Uri;
public readonly children!: ReadonlyMap<string, TestItemImpl>;
public readonly parent!: TestItemImpl | undefined;
constructor(hookRef: () => ITestItemHook | undefined) {
this.#hookRef = hookRef;
}
public add(child: vscode.TestItem) {
const map = this.#map;
const hook = this.#hookRef();
const existing = map.get(child.id);
if (existing === child) {
return;
}
if (existing) {
hook?.delete(child.id);
}
map.set(child.id, child);
hook?.created(child);
}
public get(id: string) {
return this.#map.get(id);
}
public clear() {
for (const key of this.#map.keys()) {
this.delete(key);
}
}
public delete(childOrId: vscode.TestItem | string) {
const id = typeof childOrId === 'string' ? childOrId : childOrId.id;
if (this.#map.has(id)) {
this.#map.delete(id);
this.#hookRef()?.delete(id);
}
}
public toJSON() {
return [...this.#map.values()];
}
public [Symbol.iterator]() {
return this.#map.values();
}
}
export class TestItem implements vscode.TestItem {
public id!: string;
public range!: vscode.Range | undefined;
public description!: string | undefined;
public runnable!: boolean;
public debuggable!: boolean;
public children!: TestChildrenCollection;
public uri!: vscode.Uri;
public [TestItemHookProperty]!: ITestItemHook | undefined;
public error!: string | vscode.MarkdownString;
public status!: vscode.TestItemStatus;
/** Extension-owned resolve handler */
public resolveHandler?: (token: vscode.CancellationToken) => void;
constructor(id: string, public label: string, uri: vscode.Uri, public data: unknown) {
const api = getPrivateApiFor(this);
constructor(id: string, public label: string, uri: vscode.Uri, public expandable: boolean) {
Object.defineProperties(this, {
id: {
value: id,
@@ -3342,38 +3310,52 @@ export class TestItem implements vscode.TestItem {
enumerable: true,
writable: false,
},
parent: {
enumerable: false,
get: () => api.parent,
},
children: {
value: new TestChildrenCollection(() => this[TestItemHookProperty]),
value: new ReadonlyMapView(api.children),
enumerable: true,
writable: false,
},
[TestItemHookProperty]: {
enumerable: false,
writable: true,
configurable: false,
},
range: testItemPropAccessor(this, 'range', undefined),
description: testItemPropAccessor(this, 'description', undefined),
runnable: testItemPropAccessor(this, 'runnable', true),
debuggable: testItemPropAccessor(this, 'debuggable', true),
range: testItemPropAccessor(api, 'range', undefined, rangeComparator),
description: testItemPropAccessor(api, 'description', undefined, strictEqualComparator),
runnable: testItemPropAccessor(api, 'runnable', true, strictEqualComparator),
debuggable: testItemPropAccessor(api, 'debuggable', true, strictEqualComparator),
status: testItemPropAccessor(api, 'status', TestItemStatus.Resolved, strictEqualComparator),
error: testItemPropAccessor(api, 'error', undefined, strictEqualComparator),
});
}
public invalidate() {
this[TestItemHookProperty]?.invalidate(this.id);
getPrivateApiFor(this).bus.fire([ExtHostTestItemEventType.Invalidated]);
}
public discoverChildren(progress: vscode.Progress<{ busy: boolean }>, _token: vscode.CancellationToken) {
progress.report({ busy: false });
public dispose() {
const api = getPrivateApiFor(this);
if (api.parent) {
getPrivateApiFor(api.parent).children.delete(this.id);
}
api.bus.fire([ExtHostTestItemEventType.Disposed]);
}
public addChild(child: vscode.TestItem<unknown>) {
if (!(child instanceof TestItemImpl)) {
throw new Error('Test child must be created through vscode.test.createTestItem()');
}
const api = getPrivateApiFor(this);
if (api.children.has(child.id)) {
throw new Error(`Attempted to insert a duplicate test item ID ${child.id}`);
}
api.children.set(child.id, child);
api.bus.fire([ExtHostTestItemEventType.NewChild, child]);
}
}
export class TestState implements vscode.TestState {
public messages: TestMessage[] = [];
public duration?: number;
constructor(public state: TestResult) { }
}
export class TestMessage implements vscode.TestMessage {
public severity = TestMessageSeverity.Error;
@@ -3402,7 +3384,7 @@ export enum ExternalUriOpenerPriority {
export enum WorkspaceTrustState {
Untrusted = 0,
Trusted = 1,
Unknown = 2
Unspecified = 2
}
export enum PortAutoForwardAction {

View File

@@ -3,11 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { deserializeWebviewMessage } from 'vs/workbench/api/common/extHostWebviewMessaging';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import type * as vscode from 'vscode';
@@ -28,6 +31,8 @@ export class ExtHostWebview implements vscode.Webview {
#isDisposed: boolean = false;
#hasCalledAsWebviewUri = false;
#serializeBuffersForPostMessage = false;
constructor(
handle: extHostProtocol.WebviewHandle,
proxy: extHostProtocol.MainThreadWebviewsShape,
@@ -43,6 +48,7 @@ export class ExtHostWebview implements vscode.Webview {
this.#initData = initData;
this.#workspace = workspace;
this.#extension = extension;
this.#serializeBuffersForPostMessage = shouldSerializeBuffersForPostMessage(extension);
this.#deprecationService = deprecationService;
}
@@ -104,7 +110,8 @@ export class ExtHostWebview implements vscode.Webview {
if (this.#isDisposed) {
return false;
}
return this.#proxy.$postMessage(this.#handle, message);
const serialized = serializeMessage(message, { serializeBuffersForPostMessage: this.#serializeBuffersForPostMessage });
return this.#proxy.$postMessage(this.#handle, serialized.message, ...serialized.buffers);
}
private assertNotDisposed() {
@@ -114,6 +121,49 @@ export class ExtHostWebview implements vscode.Webview {
}
}
export function shouldSerializeBuffersForPostMessage(extension: IExtensionDescription): boolean {
if (!extension.enableProposedApi) {
return false;
}
try {
const version = normalizeVersion(parseVersion(extension.engines.vscode));
return !!version && version.majorBase >= 1 && version.minorBase >= 56;
} catch {
return false;
}
}
export function serializeMessage(message: any, options: { serializeBuffersForPostMessage?: boolean }): { message: string, buffers: VSBuffer[] } {
if (options.serializeBuffersForPostMessage) {
// Extract all ArrayBuffers from the message and replace them with references.
const vsBuffers: Array<{ original: ArrayBuffer, vsBuffer: VSBuffer }> = [];
const replacer = (_key: string, value: any) => {
if (value && value instanceof ArrayBuffer) {
let index = vsBuffers.findIndex(x => x.original === value);
if (index === -1) {
const bytes = new Uint8Array(value);
const vsBuffer = VSBuffer.wrap(bytes);
index = vsBuffers.length;
vsBuffers.push({ original: value, vsBuffer });
}
return <extHostProtocol.WebviewMessageArrayBufferReference>{
$$vscode_array_buffer_reference$$: true,
index,
};
}
return value;
};
const serializedMessage = JSON.stringify(message, replacer);
return { message: serializedMessage, buffers: vsBuffers.map(x => x.vsBuffer) };
} else {
return { message: JSON.stringify(message), buffers: [] };
}
}
export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
private readonly _webviewProxy: extHostProtocol.MainThreadWebviewsShape;
@@ -132,10 +182,12 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
public $onMessage(
handle: extHostProtocol.WebviewHandle,
message: any
jsonMessage: string,
...buffers: VSBuffer[]
): void {
const webview = this.getWebview(handle);
if (webview) {
const { message } = deserializeWebviewMessage(jsonMessage, buffers);
webview._onMessageEmitter.fire(message);
}
}

View File

@@ -0,0 +1,122 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import * as extHostProtocol from './extHost.protocol';
class ArrayBufferSet {
public readonly buffers: ArrayBuffer[] = [];
public add(buffer: ArrayBuffer): number {
let index = this.buffers.indexOf(buffer);
if (index < 0) {
index = this.buffers.length;
this.buffers.push(buffer);
}
return index;
}
}
export function serializeWebviewMessage(
message: any,
transfer?: readonly ArrayBuffer[]
): { message: string, buffers: VSBuffer[] } {
if (transfer) {
// Extract all ArrayBuffers from the message and replace them with references.
const arrayBuffers = new ArrayBufferSet();
const replacer = (_key: string, value: any) => {
if (value instanceof ArrayBuffer) {
const index = arrayBuffers.add(value);
return <extHostProtocol.WebviewMessageArrayBufferReference>{
$$vscode_array_buffer_reference$$: true,
index,
};
} else if (ArrayBuffer.isView(value)) {
const type = getTypedArrayType(value);
if (type) {
const index = arrayBuffers.add(value.buffer);
return <extHostProtocol.WebviewMessageArrayBufferReference>{
$$vscode_array_buffer_reference$$: true,
index,
view: {
type: type,
byteLength: value.byteLength,
byteOffset: value.byteOffset,
}
};
}
}
return value;
};
const serializedMessage = JSON.stringify(message, replacer);
const buffers = arrayBuffers.buffers.map(arrayBuffer => {
const bytes = new Uint8Array(arrayBuffer);
return VSBuffer.wrap(bytes);
});
return { message: serializedMessage, buffers };
} else {
return { message: JSON.stringify(message), buffers: [] };
}
}
function getTypedArrayType(value: ArrayBufferView): extHostProtocol.WebviewMessageArrayBufferViewType | undefined {
switch (value.constructor.name) {
case 'Int8Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Int8Array;
case 'Uint8Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint8Array;
case 'Uint8ClampedArray': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint8ClampedArray;
case 'Int16Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Int16Array;
case 'Uint16Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint16Array;
case 'Int32Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Int32Array;
case 'Uint32Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Uint32Array;
case 'Float32Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Float32Array;
case 'Float64Array': return extHostProtocol.WebviewMessageArrayBufferViewType.Float64Array;
case 'BigInt64Array': return extHostProtocol.WebviewMessageArrayBufferViewType.BigInt64Array;
case 'BigUint64Array': return extHostProtocol.WebviewMessageArrayBufferViewType.BigUint64Array;
}
return undefined;
}
export function deserializeWebviewMessage(jsonMessage: string, buffers: VSBuffer[]): { message: any, arrayBuffers: ArrayBuffer[] } {
const arrayBuffers: ArrayBuffer[] = buffers.map(buffer => {
const arrayBuffer = new ArrayBuffer(buffer.byteLength);
const uint8Array = new Uint8Array(arrayBuffer);
uint8Array.set(buffer.buffer);
return arrayBuffer;
});
const reviver = !buffers.length ? undefined : (_key: string, value: any) => {
if (typeof value === 'object' && (value as extHostProtocol.WebviewMessageArrayBufferReference).$$vscode_array_buffer_reference$$) {
const ref = value as extHostProtocol.WebviewMessageArrayBufferReference;
const { index } = ref;
const arrayBuffer = arrayBuffers[index];
if (ref.view) {
switch (ref.view.type) {
case extHostProtocol.WebviewMessageArrayBufferViewType.Int8Array: return new Int8Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Int8Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Uint8Array: return new Uint8Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint8Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Uint8ClampedArray: return new Uint8ClampedArray(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint8ClampedArray.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Int16Array: return new Int16Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Int16Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Uint16Array: return new Uint16Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint16Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Int32Array: return new Int32Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Int32Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Uint32Array: return new Uint32Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Uint32Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Float32Array: return new Float32Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Float32Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.Float64Array: return new Float64Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / Float64Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.BigInt64Array: return new BigInt64Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / BigInt64Array.BYTES_PER_ELEMENT);
case extHostProtocol.WebviewMessageArrayBufferViewType.BigUint64Array: return new BigUint64Array(arrayBuffer, ref.view.byteOffset, ref.view.byteLength / BigUint64Array.BYTES_PER_ELEMENT);
default: throw new Error('Unknown array buffer view type');
}
}
return arrayBuffer;
}
return value;
};
const message = JSON.parse(jsonMessage, reviver);
return { message, arrayBuffers };
}

View File

@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { serializeWebviewOptions, ExtHostWebview, ExtHostWebviews, toExtensionData } from 'vs/workbench/api/common/extHostWebview';
import { serializeWebviewOptions, ExtHostWebview, ExtHostWebviews, toExtensionData, shouldSerializeBuffersForPostMessage } from 'vs/workbench/api/common/extHostWebview';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { EditorGroupColumn } from 'vs/workbench/common/editor';
import type * as vscode from 'vscode';
@@ -60,7 +60,7 @@ class ExtHostWebviewPanel extends Disposable implements vscode.WebviewPanel {
this.#webview = webview;
}
public dispose() {
public override dispose() {
if (this.#isDisposed) {
return;
}
@@ -199,11 +199,13 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
preserveFocus: typeof showOptions === 'object' && !!showOptions.preserveFocus
};
const serializeBuffersForPostMessage = shouldSerializeBuffersForPostMessage(extension);
const handle = ExtHostWebviewPanels.newHandle();
this._proxy.$createWebviewPanel(toExtensionData(extension), handle, viewType, {
title,
panelOptions: serializeWebviewPanelOptions(options),
webviewOptions: serializeWebviewOptions(extension, this.workspace, options),
serializeBuffersForPostMessage,
}, webviewShowOptions);
const webview = this.webviews.createNewWebview(handle, options, extension);
@@ -263,7 +265,9 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
}
this._serializers.set(viewType, { serializer, extension });
this._proxy.$registerSerializer(viewType);
this._proxy.$registerSerializer(viewType, {
serializeBuffersForPostMessage: shouldSerializeBuffersForPostMessage(extension)
});
return new extHostTypes.Disposable(() => {
this._serializers.delete(viewType);

View File

@@ -43,7 +43,7 @@ class ExtHostWebviewView extends Disposable implements vscode.WebviewView {
this.#isVisible = isVisible;
}
public dispose() {
public override dispose() {
if (this.#isDisposed) {
return;
}
@@ -146,7 +146,10 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS
}
this._viewProviders.set(viewType, { provider, extension });
this._proxy.$registerWebviewViewProvider(toExtensionData(extension), viewType, webviewOptions);
this._proxy.$registerWebviewViewProvider(toExtensionData(extension), viewType, {
retainContextWhenHidden: webviewOptions?.retainContextWhenHidden,
serializeBuffersForPostMessage: false,
});
return new extHostTypes.Disposable(() => {
this._viewProviders.delete(viewType);

View File

@@ -20,12 +20,11 @@ import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { Severity } from 'vs/platform/notification/common/notification';
import { WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust';
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { Range, RelativePattern, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search';
import * as vscode from 'vscode';
@@ -169,8 +168,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
private readonly _onDidChangeWorkspaceTrustState = new Emitter<vscode.WorkspaceTrustStateChangeEvent>();
readonly onDidChangeWorkspaceTrustState: Event<vscode.WorkspaceTrustStateChangeEvent> = this._onDidChangeWorkspaceTrustState.event;
private readonly _onDidGrantWorkspaceTrust = new Emitter<void>();
readonly onDidGrantWorkspaceTrust: Event<void> = this._onDidGrantWorkspaceTrust.event;
private readonly _logService: ILogService;
private readonly _requestIdProvider: Counter;
@@ -185,7 +184,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = [];
private _workspaceTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown;
private _trusted: boolean = false;
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@@ -204,8 +203,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo)) : undefined;
}
$initializeWorkspace(data: IWorkspaceData | null, trustState: WorkspaceTrustState): void {
this._workspaceTrustState = trustState;
$initializeWorkspace(data: IWorkspaceData | null, trusted: boolean): void {
this._trusted = trusted;
this.$acceptWorkspaceData(data);
this._barrier.open();
}
@@ -559,17 +558,20 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
// --- trust ---
get trustState(): WorkspaceTrustState {
return this._workspaceTrustState;
get trusted(): boolean {
return this._trusted;
}
requireWorkspaceTrust(options?: vscode.WorkspaceTrustRequestOptions): Promise<WorkspaceTrustState> {
return this._proxy.$requireWorkspaceTrust(options);
requestWorkspaceTrust(options?: vscode.WorkspaceTrustRequestOptions): Promise<boolean | undefined> {
const promise = this._proxy.$requestWorkspaceTrust(options);
return options?.modal ? promise : Promise.resolve(this._trusted);
}
$onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void {
this._workspaceTrustState = state.currentTrustState;
this._onDidChangeWorkspaceTrustState.fire(Object.freeze(state));
$onDidGrantWorkspaceTrust(): void {
if (!this._trusted) {
this._trusted = true;
this._onDidGrantWorkspaceTrust.fire();
}
}
}

View File

@@ -138,6 +138,12 @@ const apiMenus: IAPIMenu[] = [
proposed: true,
supportsSubmenus: false
},
{
key: 'statusBar/remoteIndicator',
id: MenuId.StatusBarRemoteIndicatorMenu,
description: localize('menus.statusBarRemoteIndicator', "The remote indicator menu in the status bar"),
supportsSubmenus: false
},
{
key: 'view/title',
id: MenuId.ViewTitle,
@@ -212,6 +218,11 @@ const apiMenus: IAPIMenu[] = [
key: 'ports/item/origin/inline',
id: MenuId.TunnelOriginInline,
description: localize('view.tunnelOriginInline', "The Ports view item origin inline menu")
},
{
key: 'ports/item/port/inline',
id: MenuId.TunnelPortInline,
description: localize('view.tunnelPortInline', "The Ports view item port inline menu")
}
];

View File

@@ -35,12 +35,6 @@ export interface StatusPipeArgs {
type: 'status';
}
export interface RunCommandPipeArgs {
type: 'command';
command: string;
args: any[];
}
export interface ExtensionManagementPipeArgs {
type: 'extensionManagement';
list?: { showVersions?: boolean, category?: string; };
@@ -49,7 +43,7 @@ export interface ExtensionManagementPipeArgs {
force?: boolean;
}
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs;
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs;
export interface ICommandsExecuter {
executeCommand<T>(id: string, ...args: any[]): Promise<T>;
@@ -106,10 +100,6 @@ export class CLIServerBase {
case 'status':
this.getStatus(data, res);
break;
case 'command':
this.runCommand(data, res)
.catch(this.logService.error);
break;
case 'extensionManagement':
this.manageExtensions(data, res)
.catch(this.logService.error);
@@ -156,7 +146,7 @@ export class CLIServerBase {
const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined;
const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode;
const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI };
this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs);
this._commands.executeCommand('_remoteCLI.windowOpen', urisToOpen, windowOpenArgs);
}
res.writeHead(200);
res.end();
@@ -197,7 +187,7 @@ export class CLIServerBase {
private async getStatus(data: StatusPipeArgs, res: http.ServerResponse) {
try {
const status = await this._commands.executeCommand('_issues.getSystemStatus');
const status = await this._commands.executeCommand('_remoteCLI.getSystemStatus');
res.writeHead(200);
res.write(status);
res.end();
@@ -212,28 +202,6 @@ export class CLIServerBase {
}
}
private async runCommand(data: RunCommandPipeArgs, res: http.ServerResponse) {
try {
const { command, args } = data;
const result = await this._commands.executeCommand(command, ...args);
res.writeHead(200);
res.write(JSON.stringify(result), err => {
if (err) {
this.logService.error(err);
}
});
res.end();
} catch (err) {
res.writeHead(500);
res.write(String(err), err => {
if (err) {
this.logService.error(err);
}
});
res.end();
}
}
dispose(): void {
this._server.close();

View File

@@ -27,7 +27,7 @@ import { createCancelablePromise, firstParallel } from 'vs/base/common/async';
export class ExtHostDebugService extends ExtHostDebugServiceBase {
readonly _serviceBrand: undefined;
override readonly _serviceBrand: undefined;
private _integratedTerminalInstances = new DebugTerminalCollection();
private _terminalDisposedListener: IDisposable | undefined;
@@ -43,7 +43,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService);
}
protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined {
protected override createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined {
switch (adapter.type) {
case 'server':
return new SocketDebugAdapter(adapter);
@@ -55,7 +55,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
return super.createDebugAdapter(adapter, session);
}
protected daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined {
protected override daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined {
const dae = ExecutableDebugAdapter.platformAdapterExecutable(extensionRegistry.getAllExtensionDescriptions(), session.type);
if (dae) {
return new DebugAdapterExecutable(dae.command, dae.args, dae.options);
@@ -63,11 +63,11 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
return undefined;
}
protected createSignService(): ISignService | undefined {
protected override createSignService(): ISignService | undefined {
return new SignService();
}
public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise<number | undefined> {
public override async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise<number | undefined> {
if (args.kind === 'integrated') {

View File

@@ -47,23 +47,23 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann
this._appender = appender;
}
append(value: string): void {
override append(value: string): void {
super.append(value);
this._appender.append(value);
this._onDidAppend.fire();
}
update(): void {
override update(): void {
this._appender.flush();
super.update();
}
show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
override show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
this._appender.flush();
super.show(columnOrPreserveFocus, preserveFocus);
}
clear(): void {
override clear(): void {
this._appender.flush();
super.clear();
}
@@ -85,7 +85,7 @@ export class ExtHostOutputService2 extends ExtHostOutputService {
this._logsLocation = initData.logsLocation;
}
$setVisibleChannel(channelId: string): void {
override $setVisibleChannel(channelId: string): void {
if (channelId) {
const channel = this._channels.get(channelId);
if (channel) {
@@ -94,7 +94,7 @@ export class ExtHostOutputService2 extends ExtHostOutputService {
}
}
createOutputChannel(name: string): vscode.OutputChannel {
override createOutputChannel(name: string): vscode.OutputChannel {
name = name.trim();
if (!name) {
throw new Error('illegal argument `name`. must not be falsy');

View File

@@ -59,7 +59,7 @@ export class NativeExtHostSearch extends ExtHostSearch {
});
}
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise<ISearchCompleteStats> {
override $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise<ISearchCompleteStats> {
const query = reviveQuery(rawQuery);
if (handle === this._internalFileSearchHandle) {
return this.doInternalFileSearch(handle, session, query, token);
@@ -91,7 +91,7 @@ export class NativeExtHostSearch extends ExtHostSearch {
return <Promise<ISearchCompleteStats>>this._internalFileSearchProvider.doFileSearch(rawQuery, onResult, token);
}
$clearCache(cacheKey: string): Promise<void> {
override $clearCache(cacheKey: string): Promise<void> {
if (this._internalFileSearchProvider) {
this._internalFileSearchProvider.clearCache(cacheKey);
}
@@ -99,7 +99,7 @@ export class NativeExtHostSearch extends ExtHostSearch {
return super.$clearCache(cacheKey);
}
protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager {
protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager {
return new NativeTextSearchManager(query, provider);
}
}

View File

@@ -44,6 +44,12 @@ export class ExtHostTask extends ExtHostTaskBase {
authority: initData.remote.authority,
platform: process.platform
});
} else {
this.registerTaskSystem(Schemas.file, {
scheme: Schemas.file,
authority: '',
platform: process.platform
});
}
this._proxy.$registerSupportedExecutions(true, true, true);
}
@@ -147,19 +153,19 @@ export class ExtHostTask extends ExtHostTaskBase {
}
};
for (let variable of toResolve.variables) {
result.variables[variable] = resolver.resolve(ws, variable);
result.variables[variable] = await resolver.resolveAsync(ws, variable);
}
if (toResolve.process !== undefined) {
let paths: string[] | undefined = undefined;
if (toResolve.process.path !== undefined) {
paths = toResolve.process.path.split(path.delimiter);
for (let i = 0; i < paths.length; i++) {
paths[i] = resolver.resolve(ws, paths[i]);
paths[i] = await resolver.resolveAsync(ws, paths[i]);
}
}
result.process = await win32.findExecutable(
resolver.resolve(ws, toResolve.process.name),
toResolve.process.cwd !== undefined ? resolver.resolve(ws, toResolve.process.cwd) : undefined,
await resolver.resolveAsync(ws, toResolve.process.name),
toResolve.process.cwd !== undefined ? await resolver.resolveAsync(ws, toResolve.process.cwd) : undefined,
paths
);
}

View File

@@ -8,6 +8,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { generateUuid } from 'vs/base/common/uuid';
import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell';
import { ILogService } from 'vs/platform/log/common/log';
import { SafeConfigProvider } from 'vs/platform/terminal/common/terminal';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfigProvider, ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
@@ -16,7 +17,7 @@ import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/work
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService';
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { ITerminalConfiguration, ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { detectAvailableProfiles } from 'vs/workbench/contrib/terminal/node/terminalProfiles';
import type * as vscode from 'vscode';
@@ -27,8 +28,6 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
private _variableResolverPromise: Promise<ExtHostVariableResolverService>;
private _lastActiveWorkspace: IWorkspaceFolder | undefined;
// TODO: Pull this from main side
private _isWorkspaceShellAllowed: boolean = false;
private _defaultShell: string | undefined;
constructor(
@@ -43,7 +42,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
// Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous
// and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are
// starting up but if not, we run getSystemShellSync below which gets a sane default.
getSystemShell(platform.platform, process.env as platform.IProcessEnvironment).then(s => this._defaultShell = s);
getSystemShell(platform.OS, process.env as platform.IProcessEnvironment).then(s => this._defaultShell = s);
this._updateLastActiveWorkspace();
this._variableResolverPromise = this._updateVariableResolver();
@@ -65,6 +64,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
withNullAsUndefined(options.shellArgs),
withNullAsUndefined(options.cwd),
withNullAsUndefined(options.env),
withNullAsUndefined(options.icon),
withNullAsUndefined(options.message),
/*options.waitOnExit*/ undefined,
withNullAsUndefined(options.strictEnv),
withNullAsUndefined(options.hideFromUser),
@@ -75,44 +76,24 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
}
public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string {
const fetchSetting = (key: string): { userValue: string | string[] | undefined, value: string | string[] | undefined, defaultValue: string | string[] | undefined } => {
const setting = configProvider
.getConfiguration(key.substr(0, key.lastIndexOf('.')))
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
return this._apiInspectConfigToPlain<string | string[]>(setting);
};
return terminalEnvironment.getDefaultShell(
fetchSetting,
this._isWorkspaceShellAllowed,
this._defaultShell ?? getSystemShellSync(platform.platform, process.env as platform.IProcessEnvironment),
this._buildSafeConfigProvider(configProvider),
this._defaultShell ?? getSystemShellSync(platform.OS, process.env as platform.IProcessEnvironment),
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
process.env.windir,
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver),
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, process.env, this._variableResolver),
this._logService,
useAutomationShell
);
}
public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string {
const fetchSetting = (key: string): { userValue: string | string[] | undefined, value: string | string[] | undefined, defaultValue: string | string[] | undefined } => {
const setting = configProvider
.getConfiguration(key.substr(0, key.lastIndexOf('.')))
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
return this._apiInspectConfigToPlain<string | string[]>(setting);
};
return terminalEnvironment.getDefaultShellArgs(fetchSetting, this._isWorkspaceShellAllowed, useAutomationShell, terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver), this._logService);
}
private _apiInspectConfigToPlain<T>(
config: { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined
): { userValue: T | undefined, value: T | undefined, defaultValue: T | undefined } {
return {
userValue: config ? config.globalValue : undefined,
value: config ? config.workspaceValue : undefined,
defaultValue: config ? config.defaultValue : undefined,
};
return terminalEnvironment.getDefaultShellArgs(
this._buildSafeConfigProvider(configProvider),
useAutomationShell,
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, process.env, this._variableResolver),
this._logService
);
}
private _registerListeners(): void {
@@ -136,9 +117,9 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
return this._variableResolver;
}
public async $getAvailableProfiles(quickLaunchOnly: boolean): Promise<ITerminalProfile[]> {
const config = await (await this._extHostConfiguration.getConfigProvider()).getConfiguration().get('terminal.integrated');
return detectAvailableProfiles(quickLaunchOnly, this._logService, config as ITerminalConfiguration, await this._variableResolverPromise, this._lastActiveWorkspace);
public async $getAvailableProfiles(configuredProfilesOnly: boolean): Promise<ITerminalProfile[]> {
const safeConfigProvider = this._buildSafeConfigProvider(await this._extHostConfiguration.getConfigProvider());
return detectAvailableProfiles(configuredProfilesOnly, safeConfigProvider, undefined, this._logService, await this._variableResolverPromise, this._lastActiveWorkspace);
}
public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
@@ -149,7 +130,16 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
};
}
public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void {
this._isWorkspaceShellAllowed = isAllowed;
// TODO: Remove when workspace trust is enabled
private _buildSafeConfigProvider(configProvider: ExtHostConfigProvider): SafeConfigProvider {
const config = configProvider.getConfiguration();
return (key: string) => {
const isWorkspaceConfigAllowed = config.get('terminal.integrated.allowWorkspaceConfiguration');
if (isWorkspaceConfigAllowed) {
return config.get(key) as any;
}
const inspected = config.inspect(key);
return inspected?.globalValue || inspected?.defaultValue;
};
}
}

View File

@@ -22,7 +22,6 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
import { MovingAverage } from 'vs/base/common/numbers';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ILogService } from 'vs/platform/log/common/log';
import { flatten } from 'vs/base/common/arrays';
class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
@@ -203,7 +202,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) ${extension.identifier} called openTunnel API for ${forward.remoteAddress.port}.`);
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) ${extension.identifier.value} called openTunnel API for ${forward.remoteAddress.host}:${forward.remoteAddress.port}.`);
const tunnel = await this._proxy.$openTunnel(forward, extension.displayName);
if (tunnel) {
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => {
@@ -240,17 +239,20 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise<ProvidedPortAttributes[]> {
const providedAttributes = await Promise.all(handles.map(handle => {
const providedAttributes: vscode.ProviderResult<vscode.PortAttributes>[] = [];
for (const handle of handles) {
const provider = this._portAttributesProviders.get(handle);
if (!provider) {
return [];
}
return provider.provider.providePortAttributes(ports, pid, commandline, cancellationToken);
}));
providedAttributes.push(...(await Promise.all(ports.map(async (port) => {
return provider.provider.providePortAttributes(port, pid, commandline, cancellationToken);
}))));
}
const allAttributes = <vscode.PortAttributes[][]>providedAttributes.filter(attribute => !!attribute && attribute.length > 0);
const allAttributes = <vscode.PortAttributes[]>providedAttributes.filter(attribute => !!attribute);
return (allAttributes.length > 0) ? flatten(allAttributes).map(attributes => {
return (allAttributes.length > 0) ? allAttributes.map(attributes => {
return {
autoForwardAction: <ProvidedOnAutoForward><unknown>attributes.autoForwardAction,
port: attributes.port
@@ -282,17 +284,19 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
// Do not wait for any of the proxy promises here.
// It will delay startup and there is nothing that needs to be waited for.
if (provider) {
if (provider.candidatePortSource !== undefined) {
await this._proxy.$setCandidatePortSource(provider.candidatePortSource);
this._proxy.$setCandidatePortSource(provider.candidatePortSource);
}
if (provider.showCandidatePort) {
this._showCandidatePort = provider.showCandidatePort;
await this._proxy.$setCandidateFilter();
this._proxy.$setCandidateFilter();
}
if (provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
elevation: false,
public: false
});

View File

@@ -70,7 +70,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
protected async _beforeAlmostReadyToRunExtensions(): Promise<void> {
const mainThreadConsole = this._extHostContext.getProxy(MainContext.MainThreadConsole);
wrapConsoleMethods(mainThreadConsole);
wrapConsoleMethods(mainThreadConsole, this._initData.environment.isExtensionDevelopmentDebug);
// initialize API and register actors
const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors);
@@ -173,15 +173,19 @@ function ensureSuffix(path: string, suffix: string): string {
}
// copied from bootstrap-fork.js
function wrapConsoleMethods(service: MainThreadConsoleShape) {
function wrapConsoleMethods(service: MainThreadConsoleShape, callToNative: boolean) {
wrap('info', 'log');
wrap('log', 'log');
wrap('warn', 'warn');
wrap('error', 'error');
function wrap(method: 'error' | 'warn' | 'info' | 'log', severity: 'error' | 'warn' | 'log') {
const original = console[method];
console[method] = function () {
service.$logExtensionHostMessage({ type: '__$console', severity, arguments: safeToArray(arguments) });
if (callToNative) {
original.apply(console, arguments as any);
}
};
}

View File

@@ -28,6 +28,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
class InspectContextKeysAction extends Action2 {
@@ -263,15 +264,24 @@ class LogWorkingCopiesAction extends Action2 {
});
}
run(accessor: ServicesAccessor): void {
async run(accessor: ServicesAccessor): Promise<void> {
const workingCopyService = accessor.get(IWorkingCopyService);
const workingCopyBackupService = accessor.get(IWorkingCopyBackupService);
const logService = accessor.get(ILogService);
const backups = await workingCopyBackupService.getBackups();
const msg = [
`Dirty Working Copies:`,
...workingCopyService.dirtyWorkingCopies.map(workingCopy => workingCopy.resource.toString(true)),
``,
`All Working Copies:`,
...workingCopyService.workingCopies.map(workingCopy => workingCopy.resource.toString(true)),
`[Working Copies]`,
...(workingCopyService.workingCopies.length > 0) ?
workingCopyService.workingCopies.map(workingCopy => `${workingCopy.isDirty() ? '● ' : ''}${workingCopy.resource.toString(true)} (typeId: ${workingCopy.typeId || '<no typeId>'})`) :
['<none>'],
``,
`[Backups]`,
...(backups.length > 0) ?
backups.map(backup => `${backup.resource.toString(true)} (typeId: ${backup.typeId || '<no typeId>'})`) :
['<none>'],
];
logService.info(msg.join('\n'));

View File

@@ -139,7 +139,7 @@ export class ToggleSidebarPositionAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
const position = this.layoutService.getSideBarPosition();
const newPositionValue = (position === Position.LEFT) ? 'right' : 'left';
@@ -244,7 +244,7 @@ export class ToggleEditorVisibilityAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.layoutService.toggleMaximizedPanel();
}
}
@@ -339,7 +339,7 @@ export class ToggleStatusbarVisibilityAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART);
const newVisibilityValue = !visibility;
@@ -376,7 +376,7 @@ class ToggleTabsVisibilityAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
const visibility = this.configurationService.getValue<string>(ToggleTabsVisibilityAction.tabsVisibleKey);
const newVisibilityValue = !visibility;
@@ -405,7 +405,7 @@ class ToggleZenMode extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.layoutService.toggleZenMode();
}
}
@@ -448,7 +448,7 @@ export class ToggleMenuBarAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.layoutService.toggleMenuBar();
}
}
@@ -482,7 +482,7 @@ export class ResetViewLocationsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.viewDescriptorService.reset();
}
}
@@ -585,7 +585,7 @@ export class MoveViewAction extends Action {
});
}
async run(): Promise<void> {
override async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
let viewId: string;
@@ -624,7 +624,7 @@ export class MoveFocusedViewAction extends Action {
super(id, label);
}
async run(viewId: string): Promise<void> {
override async run(viewId: string): Promise<void> {
const focusedViewId = viewId || FocusedViewContext.getValue(this.contextKeyService);
if (focusedViewId === undefined || focusedViewId.trim() === '') {
@@ -744,7 +744,7 @@ export class ResetFocusedViewLocationAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
let viewDescriptor: IViewDescriptor | null = null;

View File

@@ -32,7 +32,7 @@ abstract class BaseNavigationAction extends Action {
super(id, label);
}
async run(): Promise<boolean | IViewlet | IPanel> {
override async run(): Promise<void> {
const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART);
const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART);
const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART);
@@ -41,7 +41,7 @@ abstract class BaseNavigationAction extends Action {
if (isEditorFocus) {
const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction));
if (didNavigate) {
return true;
return;
}
neighborPart = this.layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction);
@@ -56,18 +56,12 @@ abstract class BaseNavigationAction extends Action {
}
if (neighborPart === Parts.EDITOR_PART) {
return this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST);
this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST);
} else if (neighborPart === Parts.SIDEBAR_PART) {
this.navigateToSidebar();
} else if (neighborPart === Parts.PANEL_PART) {
this.navigateToPanel();
}
if (neighborPart === Parts.SIDEBAR_PART) {
return this.navigateToSidebar();
}
if (neighborPart === Parts.PANEL_PART) {
return this.navigateToPanel();
}
return false;
}
private async navigateToPanel(): Promise<IPanel | boolean> {
@@ -240,7 +234,7 @@ export class FocusNextPart extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
focusNextOrPreviousPart(this.layoutService, this.editorService, true);
}
}
@@ -258,7 +252,7 @@ export class FocusPreviousPart extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
focusNextOrPreviousPart(this.layoutService, this.editorService, false);
}
}

View File

@@ -42,7 +42,7 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo
// Cut / Copy / Paste
new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => document.execCommand('cut')),
new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => document.execCommand('copy')),
new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async (element: HTMLInputElement | HTMLTextAreaElement) => {
new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => {
// Native: paste is supported
if (isNative) {

View File

@@ -78,7 +78,7 @@ abstract class BaseOpenRecentAction extends Action {
protected abstract isQuickNavigate(): boolean;
async run(): Promise<void> {
override async run(): Promise<void> {
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces();
@@ -286,7 +286,7 @@ class ToggleFullScreenAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.hostService.toggleFullScreen();
}
}
@@ -304,10 +304,8 @@ export class ReloadWindowAction extends Action {
super(id, label);
}
async run(): Promise<boolean> {
override async run(): Promise<void> {
await this.hostService.reload();
return true;
}
}
@@ -324,7 +322,7 @@ class ShowAboutDialogAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.dialogService.about();
}
}
@@ -342,7 +340,7 @@ export class NewWindowAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.hostService.openWindow({ remoteAuthority: null });
}
}

View File

@@ -24,6 +24,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { WORKSPACE_TRUST_ENABLED } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
export class OpenFileAction extends Action {
@@ -38,7 +39,7 @@ export class OpenFileAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
@@ -56,7 +57,7 @@ export class OpenFolderAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
@@ -74,7 +75,7 @@ export class OpenFileFolderAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
@@ -92,7 +93,7 @@ export class OpenWorkspaceAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data });
}
}
@@ -113,7 +114,7 @@ export class CloseWorkspaceAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close."));
return;
@@ -139,7 +140,7 @@ export class OpenWorkspaceConfigFileAction extends Action {
this.enabled = !!this.workspaceContextService.getWorkspace().configuration;
}
async run(): Promise<void> {
override async run(): Promise<void> {
const configuration = this.workspaceContextService.getWorkspace().configuration;
if (configuration) {
await this.editorService.openEditor({ resource: configuration, options: { pinned: true } });
@@ -160,7 +161,7 @@ export class AddRootFolderAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID);
}
}
@@ -180,7 +181,7 @@ export class GlobalRemoveRootFolderAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const state = this.contextService.getWorkbenchState();
// Workspace / Folder
@@ -208,7 +209,7 @@ export class SaveWorkspaceAsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath();
if (configPathUri && hasWorkspaceFileExtension(configPathUri)) {
switch (this.contextService.getWorkbenchState()) {
@@ -240,7 +241,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const folders = this.workspaceContextService.getWorkspace().folders;
const remoteAuthority = this.environmentService.remoteAuthority;
@@ -256,7 +257,7 @@ class WorkspaceTrustManageAction extends Action2 {
super({
id: 'workbench.action.manageTrust',
title: { value: localize('manageTrustAction', "Manage Workspace Trust"), original: 'Manage Workspace Trust' },
precondition: ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true),
precondition: ContextKeyExpr.and(IsWebContext.negate(), ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true)),
category: localize('workspacesCategory', "Workspaces"),
f1: true
});

View File

@@ -17,11 +17,12 @@ import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/qu
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileDialogService, IPickAndOpenOptions } from 'vs/platform/dialogs/common/dialogs';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IOpenWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/windows';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder';
export const ADD_ROOT_FOLDER_LABEL = localize('addFolderToWorkspace', "Add Folder to Workspace...");
@@ -60,12 +61,14 @@ CommandsRegistry.registerCommand({
handler: async (accessor) => {
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
const dialogsService = accessor.get(IFileDialogService);
const pathService = accessor.get(IPathService);
const folders = await dialogsService.showOpenDialog({
openLabel: mnemonicButtonLabel(localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")),
title: localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"),
canSelectFolders: true,
canSelectMany: true,
defaultUri: await dialogsService.defaultFolderPath()
defaultUri: await dialogsService.defaultFolderPath(),
availableFileSystems: [pathService.defaultUriScheme]
});
if (!folders || !folders.length) {
@@ -127,21 +130,28 @@ interface IOpenFolderAPICommandOptions {
forceNewWindow?: boolean;
forceReuseWindow?: boolean;
noRecentEntry?: boolean;
forceLocalWindow?: boolean;
}
CommandsRegistry.registerCommand({
id: 'vscode.openFolder',
handler: (accessor: ServicesAccessor, uri?: URI, arg?: boolean | IOpenFolderAPICommandOptions) => {
const commandService = accessor.get(ICommandService);
// Be compatible to previous args by converting to options
if (typeof arg === 'boolean') {
arg = { forceNewWindow: arg };
}
// Without URI, ask to pick a folder or workpsace to open
// Without URI, ask to pick a folder or workspace to open
if (!uri) {
return commandService.executeCommand('_files.pickFolderAndOpen', { forceNewWindow: arg?.forceNewWindow });
const options: IPickAndOpenOptions = {
forceNewWindow: arg?.forceNewWindow
};
if (arg?.forceLocalWindow) {
options.remoteAuthority = null;
options.availableFileSystems = ['file'];
}
return commandService.executeCommand('_files.pickFolderAndOpen', options);
}
uri = URI.revive(uri);
@@ -149,17 +159,28 @@ CommandsRegistry.registerCommand({
const options: IOpenWindowOptions = {
forceNewWindow: arg?.forceNewWindow,
forceReuseWindow: arg?.forceReuseWindow,
noRecentEntry: arg?.noRecentEntry
noRecentEntry: arg?.noRecentEntry,
remoteAuthority: arg?.forceLocalWindow ? null : undefined
};
const uriToOpen: IWindowOpenable = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri };
return commandService.executeCommand('_files.windowOpen', [uriToOpen], options);
},
description: {
description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.',
args: [
{ name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI },
{ name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' }
{
name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder',
constraint: (value: any) => value === undefined || value === null || value instanceof URI
},
{
name: 'options',
description: '(optional) Options. Object with the following properties: ' +
'`forceNewWindow`: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. ' +
'`forceReuseWindow`: Whether to force opening the folder/workspace in the same window. Defaults to false. ' +
'`noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to false. ' +
'Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.',
constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean'
}
]
}
});

View File

@@ -76,10 +76,9 @@ export class RangeHighlightDecorations extends Disposable {
}
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
const activeEditor = this.editorService.activeEditor;
const resource = activeEditor?.resource;
if (resource && isEqual(resource, resourceRange.resource)) {
return this.editorService.activeTextEditorControl as ICodeEditor;
const resource = this.editorService.activeEditor?.resource;
if (resource && isEqual(resource, resourceRange.resource) && isCodeEditor(this.editorService.activeTextEditorControl)) {
return this.editorService.activeTextEditorControl;
}
return undefined;
@@ -122,7 +121,7 @@ export class RangeHighlightDecorations extends Disposable {
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
}
dispose() {
override dispose() {
super.dispose();
if (this.editor?.getModel()) {
@@ -201,7 +200,7 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget {
this.editor.addOverlayWidget(this);
}
dispose(): void {
override dispose(): void {
this.editor.removeOverlayWidget(this);
super.dispose();
@@ -292,7 +291,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit
this.openWorkspaceButton = undefined;
}
dispose(): void {
override dispose(): void {
this.disposeOpenWorkspaceWidgetRenderer();
super.dispose();

View File

@@ -118,7 +118,7 @@ export abstract class Composite extends Component implements IComposite {
this.parent = parent;
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
}
@@ -161,7 +161,7 @@ export abstract class Composite extends Component implements IComposite {
/**
* Returns an array of actions to show in the action bar of the composite.
*/
getActions(): ReadonlyArray<IAction> {
getActions(): readonly IAction[] {
return [];
}
@@ -169,14 +169,14 @@ export abstract class Composite extends Component implements IComposite {
* Returns an array of actions to show in the action bar of the composite
* in a less prominent way then action from getActions.
*/
getSecondaryActions(): ReadonlyArray<IAction> {
getSecondaryActions(): readonly IAction[] {
return [];
}
/**
* Returns an array of actions to show in the context menu of the composite
*/
getContextMenuActions(): ReadonlyArray<IAction> {
getContextMenuActions(): readonly IAction[] {
return [];
}

View File

@@ -19,7 +19,7 @@ import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { getRemoteName, getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { isNative } from 'vs/base/common/platform';
@@ -27,9 +27,10 @@ export const WorkbenchStateContext = new RawContextKey<string>('workbenchState',
export const WorkspaceFolderCountContext = new RawContextKey<number>('workspaceFolderCount', 0, localize('workspaceFolderCount', "The number of root folders in the workspace"));
export const EmptyWorkspaceSupportContext = new RawContextKey<boolean>('emptyWorkspaceSupport', true, true);
export const DirtyWorkingCopiesContext = new RawContextKey<boolean>('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Wether there are any dirty working copies"));
export const DirtyWorkingCopiesContext = new RawContextKey<boolean>('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Whether there are any dirty working copies"));
export const RemoteNameContext = new RawContextKey<string>('remoteName', '', localize('remoteName', "The name of the remote the window is connected to or an empty string if not connected to any remote"));
export const VirtualWorkspaceContext = new RawContextKey<string>('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace if is from a virtual file system or an empty string."));
export const IsFullscreenContext = new RawContextKey<boolean>('isFullscreen', false, localize('isFullscreen', "Whether the window is in fullscreen mode"));
@@ -68,6 +69,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
private panelVisibleContext: IContextKey<boolean>;
private panelMaximizedContext: IContextKey<boolean>;
private vitualWorkspaceContext: IContextKey<string>;
constructor(
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@@ -91,6 +94,9 @@ export class WorkbenchContextKeysHandler extends Disposable {
RemoteNameContext.bindTo(this.contextKeyService).set(getRemoteName(this.environmentService.remoteAuthority) || '');
this.vitualWorkspaceContext = VirtualWorkspaceContext.bindTo(this.contextKeyService);
this.updateVirtualWorkspaceContextKey();
// Capabilities
HasWebFileSystemAccess.bindTo(this.contextKeyService).set(WebFileSystemAccess.supported(window));
@@ -161,7 +167,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
}
private registerListeners(): void {
this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys());
this.editorGroupService.whenReady.then(() => this.updateEditorContextKeys());
this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys()));
this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys()));
@@ -173,7 +179,10 @@ export class WorkbenchContextKeysHandler extends Disposable {
this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey()));
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey()));
this._register(this.contextService.onDidChangeWorkspaceFolders(() => {
this.updateWorkspaceFolderCountContextKey();
this.updateVirtualWorkspaceContextKey();
}));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) {
@@ -285,4 +294,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
private updateSideBarContextKeys(): void {
this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART));
}
private updateVirtualWorkspaceContextKey(): void {
this.vitualWorkspaceContext.set(getVirtualWorkspaceScheme(this.contextService.getWorkspace()) || '');
}
}

View File

@@ -3,13 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { hasWorkspaceFileExtension, IWorkspaceFolderCreationData, IRecentFile, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { normalize } from 'vs/base/common/path';
import { basename, isEqual } from 'vs/base/common/resources';
import { IFileService } from 'vs/platform/files/common/files';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { URI } from 'vs/base/common/uri';
import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { bufferToReadable, VSBuffer } from 'vs/base/common/buffer';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
@@ -19,7 +21,7 @@ import { MIME_BINARY } from 'vs/base/common/mime';
import { isWindows } from 'vs/base/common/platform';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor';
import { IEditorIdentifier, GroupIdentifier, IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
@@ -27,8 +29,9 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { Emitter } from 'vs/base/common/event';
import { NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy';
export interface IDraggedResource {
resource: URI;
@@ -110,7 +113,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array<ID
if (e.dataTransfer && e.dataTransfer.files) {
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const file = e.dataTransfer.files[i];
if (file?.path /* Electron only */ && !resources.some(r => r.resource.fsPath === file.path) /* prevent duplicates */) {
if (file?.path /* Electron only */ && !resources.some(resource => resource.resource.fsPath === file.path) /* prevent duplicates */) {
try {
resources.push({ resource: URI.file(file.path), isExternal: true });
} catch (error) {
@@ -126,7 +129,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array<ID
try {
const codeFiles: string[] = JSON.parse(rawCodeFiles);
codeFiles.forEach(codeFile => {
if (!resources.some(r => r.resource.fsPath === codeFile) /* prevent duplicates */) {
if (!resources.some(resource => resource.resource.fsPath === codeFile) /* prevent duplicates */) {
resources.push({ resource: URI.file(codeFile), isExternal: true });
}
});
@@ -159,7 +162,7 @@ export class ResourcesDropHandler {
@IFileService private readonly fileService: IFileService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@ITextFileService private readonly textFileService: ITextFileService,
@IBackupFileService private readonly backupFileService: IBackupFileService,
@IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService,
@IEditorService private readonly editorService: IEditorService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
@IHostService private readonly hostService: IHostService
@@ -167,7 +170,7 @@ export class ResourcesDropHandler {
}
async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled);
const untitledOrFileResources = extractResources(event).filter(resource => this.fileService.canHandleResource(resource.resource) || resource.resource.scheme === Schemas.untitled);
if (!untitledOrFileResources.length) {
return;
}
@@ -227,6 +230,7 @@ export class ResourcesDropHandler {
}
private async handleDirtyEditorDrop(droppedDirtyEditor: IDraggedEditor): Promise<boolean> {
const fileEditorFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileEditorInputFactory();
// Untitled: always ensure that we open a new untitled editor for each file we drop
if (droppedDirtyEditor.resource.scheme === Schemas.untitled) {
@@ -237,7 +241,7 @@ export class ResourcesDropHandler {
}
// File: ensure the file is not dirty or opened already
else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen({ resource: droppedDirtyEditor.resource })) {
else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpened({ resource: droppedDirtyEditor.resource, typeId: fileEditorFactory.typeId })) {
return false;
}
@@ -245,7 +249,7 @@ export class ResourcesDropHandler {
// content and turn it into a backup so that it loads the contents
if (typeof droppedDirtyEditor.dirtyContent === 'string') {
try {
await this.backupFileService.backup(droppedDirtyEditor.resource, stringToSnapshot(droppedDirtyEditor.dirtyContent));
await this.workingCopyBackupService.backup({ resource: droppedDirtyEditor.resource, typeId: NO_TYPE_ID }, bufferToReadable(VSBuffer.fromString(droppedDirtyEditor.dirtyContent)));
} catch (e) {
// Ignore error
}

View File

@@ -4,22 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Event } from 'vs/base/common/event';
import { EditorInput } from 'vs/workbench/common/editor';
import { EditorInput, EditorResourceAccessor, IEditorInput, EditorExtensions, SideBySideEditor } from 'vs/workbench/common/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { IConstructorSignature0, IInstantiationService, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { insert } from 'vs/base/common/arrays';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
export const Extensions = {
Editors: 'workbench.contributions.editors',
Associations: 'workbench.editors.associations'
};
import { Promises } from 'vs/base/common/async';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { URI } from 'vs/workbench/workbench.web.api';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
//#region Editors Registry
@@ -161,7 +158,7 @@ class EditorRegistry implements IEditorRegistry {
if (descriptors.length > 0) {
// Ask the input for its preferred Editor
const preferredEditorId = input.getPreferredEditorId(descriptors.map(d => d.getId()));
const preferredEditorId = input.getPreferredEditorId(descriptors.map(descriptor => descriptor.getId()));
if (preferredEditorId) {
return this.getEditorById(preferredEditorId);
}
@@ -194,137 +191,91 @@ class EditorRegistry implements IEditorRegistry {
}
}
Registry.add(Extensions.Editors, new EditorRegistry());
Registry.add(EditorExtensions.Editors, new EditorRegistry());
//#endregion
//#region Editor Close Tracker
//#region Editor Associations
export function whenEditorClosed(accessor: ServicesAccessor, resources: URI[]): Promise<void> {
const editorService = accessor.get(IEditorService);
const uriIdentityService = accessor.get(IUriIdentityService);
const workingCopyService = accessor.get(IWorkingCopyService);
export const editorsAssociationsSettingId = 'workbench.editorAssociations';
return new Promise(resolve => {
let remainingResources = [...resources];
export const DEFAULT_EDITOR_ASSOCIATION: IEditorType = {
id: 'default',
displayName: localize('promptOpenWith.defaultEditor.displayName', "Text Editor"),
providerDisplayName: localize('builtinProviderDisplayName', "Built-in")
};
// Observe any editor closing from this moment on
const listener = editorService.onDidCloseEditor(async event => {
const primaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.PRIMARY });
const secondaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.SECONDARY });
export type EditorAssociation = {
readonly viewType: string;
readonly filenamePattern?: string;
};
export type EditorsAssociations = readonly EditorAssociation[];
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const editorTypeSchemaAddition: IJSONSchema = {
type: 'string',
enum: []
};
const editorAssociationsConfigurationNode: IConfigurationNode = {
...workbenchConfigurationNodeBase,
properties: {
'workbench.editorAssociations': {
type: 'array',
markdownDescription: localize('editor.editorAssociations', "Configure which editor to use for specific file types."),
items: {
type: 'object',
defaultSnippets: [{
body: {
'viewType': '$1',
'filenamePattern': '$2'
}
}],
properties: {
'viewType': {
anyOf: [
{
type: 'string',
description: localize('editor.editorAssociations.viewType', "The unique id of the editor to use."),
},
editorTypeSchemaAddition
]
},
'filenamePattern': {
type: 'string',
description: localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."),
}
// Remove from resources to wait for being closed based on the
// resources from editors that got closed
remainingResources = remainingResources.filter(resource => {
if (uriIdentityService.extUri.isEqual(resource, primaryResource) || uriIdentityService.extUri.isEqual(resource, secondaryResource)) {
return false; // remove - the closing editor matches this resource
}
return true; // keep - not yet closed
});
// All resources to wait for being closed are closed
if (remainingResources.length === 0) {
// If auto save is configured with the default delay (1s) it is possible
// to close the editor while the save still continues in the background. As such
// we have to also check if the editors to track for are dirty and if so wait
// for them to get saved.
const dirtyResources = resources.filter(resource => workingCopyService.isDirty(resource));
if (dirtyResources.length > 0) {
await Promises.settled(dirtyResources.map(async resource => await new Promise<void>(resolve => {
if (!workingCopyService.isDirty(resource)) {
return resolve(); // return early if resource is not dirty
}
// Otherwise resolve promise when resource is saved
const listener = workingCopyService.onDidChangeDirty(workingCopy => {
if (!workingCopy.isDirty() && uriIdentityService.extUri.isEqual(resource, workingCopy.resource)) {
listener.dispose();
return resolve();
}
});
})));
}
listener.dispose();
return resolve();
}
}
}
};
export interface IEditorType {
readonly id: string;
readonly displayName: string;
readonly providerDisplayName: string;
}
export interface IEditorTypesHandler {
readonly onDidChangeEditorTypes: Event<void>;
getEditorTypes(): IEditorType[];
}
export interface IEditorAssociationsRegistry {
/**
* Register handlers for editor types
*/
registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable;
}
class EditorAssociationsRegistry implements IEditorAssociationsRegistry {
private readonly editorTypesHandlers = new Map<string, IEditorTypesHandler>();
registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable {
if (this.editorTypesHandlers.has(id)) {
throw new Error(`An editor type handler with ${id} was already registered.`);
}
this.editorTypesHandlers.set(id, handler);
this.updateEditorAssociationsSchema();
const editorTypeChangeEvent = handler.onDidChangeEditorTypes(() => {
this.updateEditorAssociationsSchema();
});
return {
dispose: () => {
editorTypeChangeEvent.dispose();
this.editorTypesHandlers.delete(id);
this.updateEditorAssociationsSchema();
}
};
}
private updateEditorAssociationsSchema() {
const enumValues: string[] = [];
const enumDescriptions: string[] = [];
const editorTypes: IEditorType[] = [DEFAULT_EDITOR_ASSOCIATION];
for (const [, handler] of this.editorTypesHandlers) {
editorTypes.push(...handler.getEditorTypes());
}
for (const { id, providerDisplayName } of editorTypes) {
enumValues.push(id);
enumDescriptions.push(localize('editorAssociations.viewType.sourceDescription', "Source: {0}", providerDisplayName));
}
editorTypeSchemaAddition.enum = enumValues;
editorTypeSchemaAddition.enumDescriptions = enumDescriptions;
configurationRegistry.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode);
}
});
}
//#endregion
//#region ARIA
export function computeEditorAriaLabel(input: IEditorInput, index: number | undefined, group: IEditorGroup | undefined, groupCount: number): string {
let ariaLabel = input.getAriaLabel();
if (group && !group.isPinned(input)) {
ariaLabel = localize('preview', "{0}, preview", ariaLabel);
}
if (group?.isSticky(index ?? input)) {
ariaLabel = localize('pinned', "{0}, pinned", ariaLabel);
}
// Apply group information to help identify in
// which group we are (only if more than one group
// is actually opened)
if (group && groupCount > 1) {
ariaLabel = `${ariaLabel}, ${group.ariaLabel}`;
}
return ariaLabel;
}
Registry.add(Extensions.Associations, new EditorAssociationsRegistry());
configurationRegistry.registerConfiguration(editorAssociationsConfigurationNode);
//#endregion

View File

@@ -227,7 +227,7 @@ export class ResourceLabels extends Disposable {
this.labels = [];
}
dispose(): void {
override dispose(): void {
super.dispose();
this.clear();
@@ -569,6 +569,7 @@ class ResourceLabelWidget extends IconLabel {
if (this.options.fileDecorations.badges) {
iconLabelOptions.extraClasses.push(deco.badgeClassName);
iconLabelOptions.extraClasses.push(deco.iconClassName);
}
}
}
@@ -580,7 +581,7 @@ class ResourceLabelWidget extends IconLabel {
return true;
}
dispose(): void {
override dispose(): void {
super.dispose();
this.label = undefined;

View File

@@ -7,7 +7,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo } from 'vs/base/browser/dom';
import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { Registry } from 'vs/platform/registry/common/platform';
import { isWindows, isLinux, isMacintosh, isWeb, isNative } from 'vs/base/common/platform';
import { pathsToEditors, SideBySideEditorInput } from 'vs/workbench/common/editor';
@@ -22,7 +22,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IEditor } from 'vs/editor/common/editorCommon';
@@ -159,7 +159,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private environmentService!: IWorkbenchEnvironmentService;
private extensionService!: IExtensionService;
private configurationService!: IConfigurationService;
private lifecycleService!: ILifecycleService;
private storageService!: IStorageService;
private hostService!: IHostService;
private editorService!: IEditorService;
@@ -169,7 +168,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private viewletService!: IViewletService;
private viewDescriptorService!: IViewDescriptorService;
private contextService!: IWorkspaceContextService;
private backupFileService!: IBackupFileService;
private workingCopyBackupService!: IWorkingCopyBackupService;
private notificationService!: INotificationService;
private themeService!: IThemeService;
private activityBarService!: IActivityBarService;
@@ -232,7 +231,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
wasPanelVisible: false,
transitionDisposables: new DisposableStore(),
setNotificationsFilter: false,
editorWidgetSet: new Set<IEditor>()
}
};
@@ -247,11 +245,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Services
this.environmentService = accessor.get(IWorkbenchEnvironmentService);
this.configurationService = accessor.get(IConfigurationService);
this.lifecycleService = accessor.get(ILifecycleService);
this.hostService = accessor.get(IHostService);
this.contextService = accessor.get(IWorkspaceContextService);
this.storageService = accessor.get(IStorageService);
this.backupFileService = accessor.get(IBackupFileService);
this.workingCopyBackupService = accessor.get(IWorkingCopyBackupService);
this.themeService = accessor.get(IThemeService);
this.extensionService = accessor.get(IExtensionService);
this.logService = accessor.get(ILogService);
@@ -314,7 +311,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
// Theme changes
this._register(this.themeService.onDidColorThemeChange(theme => this.updateStyles()));
this._register(this.themeService.onDidColorThemeChange(() => this.updateStyles()));
// Window focus changes
this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e)));
@@ -582,7 +579,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
const { views } = defaultLayout;
if (views?.length) {
this.state.views.defaults = views.map(v => v.id);
this.state.views.defaults = views.map(view => view.id);
}
}
@@ -599,7 +596,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Files to diff is exclusive
return pathsToEditors(initialFilesToOpen.filesToDiff, fileService).then(filesToDiff => {
if (filesToDiff?.length === 2) {
if (filesToDiff.length === 2) {
return [{
leftResource: filesToDiff[0].resource,
rightResource: filesToDiff[1].resource,
@@ -613,13 +610,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
});
}
// Empty workbench
// Empty workbench configured to open untitled file if empty
else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') {
if (this.editorGroupService.willRestoreEditors) {
return []; // do not open any empty untitled file if we restored editors from previous session
if (this.editorGroupService.hasRestorableState) {
return []; // do not open any empty untitled file if we restored groups/editors from previous session
}
return this.backupFileService.hasBackups().then(hasBackups => {
return this.workingCopyBackupService.hasBackups().then(hasBackups => {
if (hasBackups) {
return []; // do not open any empty untitled file if we have backups to restore
}
@@ -635,6 +632,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
get openedDefaultEditors() { return this._openedDefaultEditors; }
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined {
// Check for editors from `defaultLayout` options first
const defaultLayout = this.environmentService.options?.defaultLayout;
if (defaultLayout?.editors?.length && (defaultLayout.force || this.storageService.isNew(StorageScope.WORKSPACE))) {
this._openedDefaultEditors = true;
@@ -646,6 +645,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
};
}
// Then check for files to open, create or diff from main side
const { filesToOpenOrCreate, filesToDiff } = this.environmentService.configuration;
if (filesToOpenOrCreate || filesToDiff) {
return { filesToOpenOrCreate, filesToDiff };
@@ -654,17 +654,40 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return undefined;
}
protected async restoreWorkbenchLayout(): Promise<void> {
const restorePromises: Promise<void>[] = [];
private whenReadyResolve: (() => void) | undefined;
protected readonly whenReady = new Promise<void>(resolve => (this.whenReadyResolve = resolve));
private whenRestoredResolve: (() => void) | undefined;
readonly whenRestored = new Promise<void>(resolve => (this.whenRestoredResolve = resolve));
private restored = false;
isRestored(): boolean {
return this.restored;
}
protected restoreParts(): void {
// distinguish long running restore operations that
// are required for the layout to be ready from those
// that are needed to signal restoring is done
const layoutReadyPromises: Promise<unknown>[] = [];
const layoutRestoredPromises: Promise<unknown>[] = [];
// Restore editors
restorePromises.push((async () => {
layoutReadyPromises.push((async () => {
mark('code/willRestoreEditors');
// first ensure the editor part is restored
await this.editorGroupService.whenRestored;
// first ensure the editor part is ready
await this.editorGroupService.whenReady;
// then see for editors to open as instructed
// it is important that we trigger this from
// the overall restore flow to reduce possible
// flicker on startup: we want any editor to
// open to get a chance to open first before
// signaling that layout is restored, but we do
// not need to await the editors from having
// fully loaded.
let editors: IResourceEditorInputType[];
if (Array.isArray(this.state.editor.editorsToOpen)) {
editors = this.state.editor.editorsToOpen;
@@ -672,19 +695,32 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
editors = await this.state.editor.editorsToOpen;
}
let openEditorsPromise: Promise<unknown> | undefined = undefined;
if (editors.length) {
await this.editorService.openEditors(editors);
openEditorsPromise = this.editorService.openEditors(editors);
}
mark('code/didRestoreEditors');
// do not block the overall layout ready flow from potentially
// slow editors to resolve on startup
layoutRestoredPromises.push(
Promise.all([
openEditorsPromise,
this.editorGroupService.whenRestored
]).finally(() => {
// the `code/didRestoreEditors` perf mark is specifically
// for when visible editors have resolved, so we only mark
// if when editor group service has restored.
mark('code/didRestoreEditors');
})
);
})());
// Restore default views
// Restore default views (only when `IDefaultLayout` is provided)
const restoreDefaultViewsPromise = (async () => {
if (this.state.views.defaults?.length) {
mark('code/willOpenDefaultViews');
let locationsRestored: { id: string; order: number; }[] = [];
const locationsRestored: { id: string; order: number; }[] = [];
const tryOpenView = (view: { id: string; order: number; }): boolean => {
const location = this.viewDescriptorService.getViewLocationById(view.id);
@@ -742,10 +778,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
mark('code/didOpenDefaultViews');
}
})();
restorePromises.push(restoreDefaultViewsPromise);
layoutReadyPromises.push(restoreDefaultViewsPromise);
// Restore Sidebar
restorePromises.push((async () => {
layoutReadyPromises.push((async () => {
// Restoring views could mean that sidebar already
// restored, as such we need to test again
@@ -765,7 +801,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
})());
// Restore Panel
restorePromises.push((async () => {
layoutReadyPromises.push((async () => {
// Restoring views could mean that panel already
// restored, as such we need to test again
@@ -776,7 +812,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
mark('code/willRestorePanel');
const panel = await this.panelService.openPanel(this.state.panel.panelToRestore!);
const panel = await this.panelService.openPanel(this.state.panel.panelToRestore);
if (!panel) {
await this.panelService.openPanel(Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId()); // fallback to default panel as needed
}
@@ -794,8 +830,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.centerEditorLayout(true, true);
}
// Await restore to be done
await Promises.settled(restorePromises);
// Await for promises that we recorded to update
// our ready and restored states properly.
Promises.settled(layoutReadyPromises).finally(() => {
this.whenReadyResolve?.();
Promises.settled(layoutRestoredPromises).finally(() => {
this.restored = true;
this.whenRestoredResolve?.();
});
});
}
private updatePanelPosition() {
@@ -822,10 +866,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this._register(delegate.onDidChangeNotificationsVisibility(visible => this._onDidChangeNotificationsVisibility.fire(visible)));
}
isRestored(): boolean {
return this.lifecycleService.phase >= LifecyclePhase.Restored;
}
hasFocus(part: Parts): boolean {
const activeElement = document.activeElement;
if (!activeElement) {
@@ -976,22 +1016,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
editor.updateOptions({ lineNumbers });
};
const editorControlSet = this.state.zenMode.editorWidgetSet;
if (!lineNumbers) {
// Reset line numbers on all editors visible and non-visible
for (const editor of editorControlSet) {
setEditorLineNumbers(editor);
for (const editorControl of this.editorService.visibleTextEditorControls) {
setEditorLineNumbers(editorControl);
}
editorControlSet.clear();
} else {
for (const editorControl of this.editorService.visibleTextEditorControls) {
if (!editorControlSet.has(editorControl)) {
editorControlSet.add(editorControl);
this.state.zenMode.transitionDisposables.add(editorControl.onDidDispose(() => {
editorControlSet.delete(editorControl);
}));
}
setEditorLineNumbers(editorControl);
}
}
@@ -1044,9 +1075,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (config.silentNotifications) {
this.notificationService.setFilter(NotificationsFilter.ERROR);
}
this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(c => {
this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => {
const silentNotificationsKey = 'zenMode.silentNotifications';
if (c.affectsConfiguration(silentNotificationsKey)) {
if (e.affectsConfiguration(silentNotificationsKey)) {
const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF;
this.notificationService.setFilter(filter);
}
@@ -1780,7 +1811,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return result;
}
dispose(): void {
override dispose(): void {
super.dispose();
this.disposed = true;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .part {
box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
@@ -29,7 +29,7 @@
.monaco-workbench .part > .title {
height: 35px;
display: flex;
box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
@@ -73,9 +73,6 @@
.monaco-workbench .part > .title > .title-actions .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
@@ -103,6 +100,10 @@
}
.monaco-workbench .part > .content > .monaco-progress-container .progress-bit,
.monaco-workbench .part.editor > .content .monaco-progress-container .progress-bit {
.monaco-workbench
.part.editor
> .content
.monaco-progress-container
.progress-bit {
height: 2px;
}

View File

@@ -36,13 +36,13 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
super(id, telemetryService, themeService, storageService);
}
create(parent: HTMLElement): void {
override create(parent: HTMLElement): void {
this.viewPaneContainer = this._register(this.createViewPaneContainer(parent));
this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea()));
this.viewPaneContainer.create(parent);
}
setVisible(visible: boolean): void {
override setVisible(visible: boolean): void {
super.setVisible(visible);
this.viewPaneContainer?.setVisible(visible);
}
@@ -63,15 +63,15 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
return this.viewPaneContainer;
}
getActionsContext(): unknown {
override getActionsContext(): unknown {
return this.getViewPaneContainer()?.getActionsContext();
}
getContextMenuActions(): ReadonlyArray<IAction> {
override getContextMenuActions(): readonly IAction[] {
return this.viewPaneContainer?.menuActions?.getContextMenuActions() ?? [];
}
getActions(): ReadonlyArray<IAction> {
override getActions(): readonly IAction[] {
const result = [];
if (this.viewPaneContainer?.menuActions) {
result.push(...this.viewPaneContainer.menuActions.getPrimaryActions());
@@ -82,7 +82,7 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
return result;
}
getSecondaryActions(): ReadonlyArray<IAction> {
override getSecondaryActions(): readonly IAction[] {
if (!this.viewPaneContainer?.menuActions) {
return [];
}
@@ -116,19 +116,19 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
return menuActions.length ? menuActions : viewPaneActions;
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
override getActionViewItem(action: IAction): IActionViewItem | undefined {
return this.viewPaneContainer?.getActionViewItem(action);
}
getTitle(): string {
override getTitle(): string {
return this.viewPaneContainer?.getTitle() ?? '';
}
saveState(): void {
override saveState(): void {
super.saveState();
}
focus(): void {
override focus(): void {
this.viewPaneContainer?.focus();
}

View File

@@ -37,19 +37,19 @@ export abstract class Panel extends PaneComposite implements IPanel {
this._register(this.panelActions.onDidChange(() => this.updateTitleArea()));
}
getActions(): ReadonlyArray<IAction> {
override getActions(): readonly IAction[] {
return [...super.getActions(), ...this.panelActions.getPrimaryActions()];
}
getSecondaryActions(): ReadonlyArray<IAction> {
override getSecondaryActions(): readonly IAction[] {
return this.mergeSecondaryActions(super.getSecondaryActions(), this.panelActions.getSecondaryActions());
}
getContextMenuActions(): ReadonlyArray<IAction> {
override getContextMenuActions(): readonly IAction[] {
return this.mergeSecondaryActions(super.getContextMenuActions(), this.panelActions.getContextMenuActions());
}
private mergeSecondaryActions(actions: ReadonlyArray<IAction>, panelActions: IAction[]): ReadonlyArray<IAction> {
private mergeSecondaryActions(actions: readonly IAction[], panelActions: IAction[]): readonly IAction[] {
if (panelActions.length && actions.length) {
return [
...actions,

View File

@@ -52,7 +52,7 @@ export abstract class Part extends Component implements ISerializableView {
layoutService.registerPart(this);
}
protected onThemeChange(theme: IColorTheme): void {
protected override onThemeChange(theme: IColorTheme): void {
// only call if our create() method has been called
if (this.parent) {
@@ -60,7 +60,7 @@ export abstract class Part extends Component implements ISerializableView {
}
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
}

View File

@@ -16,7 +16,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { ActivityAction, ActivityActionViewItem, IActivityHoverOptions, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IActivity } from 'vs/workbench/common/activity';
import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
@@ -35,6 +35,8 @@ import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/context
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export class ViewContainerActivityAction extends ActivityAction {
@@ -56,7 +58,7 @@ export class ViewContainerActivityAction extends ActivityAction {
this.activity = activity;
}
async run(event: unknown): Promise<void> {
override async run(event: unknown): Promise<void> {
if (event instanceof MouseEvent && event.button === 2) {
return; // do not run on right click
}
@@ -111,17 +113,20 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
action: ActivityAction,
private contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
hoverOptions: IActivityHoverOptions,
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IMenuService protected readonly menuService: IMenuService,
@IContextMenuService protected readonly contextMenuService: IContextMenuService,
@IContextKeyService protected readonly contextKeyService: IContextKeyService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(action, { draggable: false, colors, icon: true, hasPopup: true }, themeService);
super(action, { draggable: false, colors, icon: true, hasPopup: true, hoverOptions }, themeService, hoverService, configurationService, keybindingService);
}
render(container: HTMLElement): void {
override render(container: HTMLElement): void {
super.render(container);
// Context menus are triggered on mouse down so that an item can be picked
@@ -190,7 +195,9 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
action: ActivityAction,
contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
activityHoverOptions: IActivityHoverOptions,
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IContextMenuService contextMenuService: IContextMenuService,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@@ -198,12 +205,13 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IProductService private readonly productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService
@IStorageService private readonly storageService: IStorageService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService);
}
protected async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
protected override async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
await super.resolveMainMenuActions(accountsMenu, disposables);
const otherCommands = accountsMenu.getActions();
@@ -279,7 +287,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
return menus;
}
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
protected override async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
const actions = await super.resolveContextMenuActions(disposables);
actions.unshift(...[
@@ -297,14 +305,17 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
action: ActivityAction,
contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
activityHoverOptions: IActivityHoverOptions,
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IMenuService menuService: IMenuService,
@IContextMenuService contextMenuService: IContextMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService);
}
}

View File

@@ -10,7 +10,7 @@ import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbenc
import { Part } from 'vs/workbench/browser/part';
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActivityActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
@@ -22,7 +22,7 @@ import { Dimension, createCSSRule, asCSSUrl, addDisposableListener, EventType }
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions';
import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService, getEnabledViewContainerContextKey } from 'vs/workbench/common/views';
import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined, isString } from 'vs/base/common/types';
@@ -43,6 +43,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { StringSHA1 } from 'vs/base/common/hash';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
interface IPlaceholderViewContainer {
readonly id: string;
@@ -156,6 +157,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, {
icon: true,
orientation: ActionsOrientation.VERTICAL,
activityHoverOptions: this.getActivityHoverOptions(),
preventLoopNavigation: true,
openComposite: compositeId => this.viewsService.openViewContainer(compositeId, true),
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
@@ -218,6 +220,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}));
}
private getActivityHoverOptions(): IActivityHoverOptions {
return {
position: () => this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT,
delay: () => 0
};
}
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
const actions: IAction[] = [];
@@ -269,7 +278,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}));
}
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>) {
private onDidChangeViewContainers(added: readonly { container: ViewContainer, location: ViewContainerLocation; }[], removed: readonly { container: ViewContainer, location: ViewContainerLocation; }[]) {
removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container));
this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container));
}
@@ -447,7 +456,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.registerKeyboardNavigationListeners();
}
createContentArea(parent: HTMLElement): HTMLElement {
override createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
this.content = document.createElement('div');
@@ -522,11 +531,11 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.globalActivityActionBar = this._register(new ActionBar(container, {
actionViewItemProvider: action => {
if (action.id === 'workbench.actions.manage') {
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme), this.getActivityHoverOptions());
}
if (action.id === 'workbench.actions.accounts') {
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme), this.getActivityHoverOptions());
}
throw new Error(`No view item for action '${action.id}'`);
@@ -598,7 +607,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return compositeActions;
}
private onDidRegisterViewContainers(viewContainers: ReadonlyArray<ViewContainer>): void {
private onDidRegisterViewContainers(viewContainers: readonly ViewContainer[]): void {
for (const viewContainer of viewContainers) {
this.addComposite(viewContainer);
@@ -765,7 +774,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.compositeBar.focus();
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
const container = assertIsDefined(this.getContainer());
@@ -790,7 +799,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
};
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) {
return;
}
@@ -814,7 +823,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;
}
private getViewContainers(): ReadonlyArray<ViewContainer> {
private getViewContainers(): readonly ViewContainer[] {
return this.viewDescriptorService.getViewContainersByLocation(this.location);
}

View File

@@ -57,6 +57,7 @@
z-index: 1;
display: flex;
overflow: hidden;
width: 48px;
height: 48px;
margin-right: 0;
box-sizing: border-box;
@@ -154,7 +155,8 @@
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content {
font-size: 12px;
font-weight: unset;
padding: 0 2px;
padding: 0;
justify-content: center;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content::before {

View File

@@ -10,7 +10,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions';
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions';
import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -145,6 +145,7 @@ export interface ICompositeBarOptions {
readonly compositeSize: number;
readonly overflowActionSize: number;
readonly dndHandler: ICompositeDragAndDrop;
readonly activityHoverOptions: IActivityHoverOptions;
readonly preventLoopNavigation?: boolean;
getActivityAction: (compositeId: string) => ActivityAction;
@@ -213,11 +214,12 @@ export class CompositeBar extends Widget implements ICompositeBar {
}
const item = this.model.findItem(action.id);
return item && this.instantiationService.createInstance(
CompositeActionViewItem, action as ActivityAction, item.pinnedAction,
CompositeActionViewItem,
{ draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions },
action as ActivityAction,
item.pinnedAction,
compositeId => this.options.getContextMenuActionsForComposite(compositeId),
() => this.getContextMenuActions(),
this.options.colors,
this.options.icon,
this.options.dndHandler,
this
);
@@ -595,7 +597,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
return item?.activity[0]?.badge;
},
this.options.getOnCompositeClickAction,
this.options.colors
this.options.colors,
this.options.activityHoverOptions
);
compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });

View File

@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { Action, IAction, Separator } from 'vs/base/common/actions';
import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getDomNodePagePosition, hide, show } from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { dispose, toDisposable, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
@@ -21,6 +21,11 @@ import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDr
import { Color } from 'vs/base/common/color';
import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { Codicon } from 'vs/base/common/codicons';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { domEvent } from 'vs/base/browser/event';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
export interface ICompositeActivity {
badge: IBadge;
@@ -101,7 +106,7 @@ export class ActivityAction extends Action {
this._onDidChangeBadge.fire(this);
}
dispose(): void {
override dispose(): void {
this._onDidChangeActivity.dispose();
this._onDidChangeBadge.dispose();
@@ -122,9 +127,15 @@ export interface ICompositeBarColors {
dragAndDropBorder?: Color;
}
export interface IActivityHoverOptions {
position: () => HoverPosition;
delay: () => number;
}
export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions {
icon?: boolean;
colors: (theme: IColorTheme) => ICompositeBarColors;
hoverOptions: IActivityHoverOptions;
hasPopup?: boolean;
}
@@ -132,22 +143,35 @@ export class ActivityActionViewItem extends BaseActionViewItem {
protected container!: HTMLElement;
protected label!: HTMLElement;
protected badge!: HTMLElement;
protected options!: IActivityActionViewItemOptions;
protected override readonly options: IActivityActionViewItemOptions;
private badgeContent: HTMLElement | undefined;
private readonly badgeDisposable = this._register(new MutableDisposable());
private mouseUpTimeout: any;
private keybindingLabel: string | undefined | null;
private readonly hoverDisposables = this._register(new DisposableStore());
private readonly hover = this._register(new MutableDisposable<IDisposable>());
private readonly showHoverScheduler = new RunOnceScheduler(() => this.showHover(), 0);
constructor(
action: ActivityAction,
options: IActivityActionViewItemOptions,
@IThemeService protected readonly themeService: IThemeService
@IThemeService protected readonly themeService: IThemeService,
@IHoverService private readonly hoverService: IHoverService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IKeybindingService protected readonly keybindingService: IKeybindingService,
) {
super(null, action, options);
this.options = options;
this._register(this.themeService.onDidColorThemeChange(this.onThemeChange, this));
this._register(action.onDidChangeActivity(this.updateActivity, this));
this._register(Event.filter(keybindingService.onDidUpdateKeybindings, () => this.keybindingLabel !== this.computeKeybindingLabel())(() => this.updateTitle()));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('workbench.experimental.useCustomHover'))(() => this.updateHover()));
this._register(action.onDidChangeBadge(this.updateBadge, this));
this._register(toDisposable(() => this.showHoverScheduler.cancel()));
}
protected get activity(): IActivity {
@@ -195,10 +219,13 @@ export class ActivityActionViewItem extends BaseActionViewItem {
}
}
render(container: HTMLElement): void {
override render(container: HTMLElement): void {
super.render(container);
this.container = container;
if (this.options.icon) {
this.container.classList.add('icon');
}
if (this.options.hasPopup) {
this.container.setAttribute('role', 'button');
@@ -239,6 +266,7 @@ export class ActivityActionViewItem extends BaseActionViewItem {
this.updateActivity();
this.updateStyles();
this.updateHover();
}
private onThemeChange(theme: IColorTheme): void {
@@ -247,7 +275,7 @@ export class ActivityActionViewItem extends BaseActionViewItem {
protected updateActivity(): void {
this.updateLabel();
this.updateTitle(this.activity.name);
this.updateTitle();
this.updateBadge();
this.updateStyles();
}
@@ -311,22 +339,10 @@ export class ActivityActionViewItem extends BaseActionViewItem {
}
}
// Title
let title: string;
if (badge?.getDescription()) {
if (this.activity.name) {
title = localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription());
} else {
title = badge.getDescription();
}
} else {
title = this.activity.name;
}
this.updateTitle(title);
this.updateTitle();
}
protected updateLabel(): void {
protected override updateLabel(): void {
this.label.className = 'action-label';
if (this.activity.cssClass) {
@@ -343,16 +359,77 @@ export class ActivityActionViewItem extends BaseActionViewItem {
}
}
private updateTitle(title: string): void {
private updateTitle(): void {
// Title
const title = this.computeTitle();
[this.label, this.badge, this.container].forEach(element => {
if (element) {
element.setAttribute('aria-label', title);
element.title = title;
if (this.useCustomHover) {
element.setAttribute('title', '');
element.removeAttribute('title');
} else {
element.setAttribute('title', title);
}
}
});
}
dispose(): void {
private computeTitle(): string {
this.keybindingLabel = this.computeKeybindingLabel();
let title = this.keybindingLabel ? localize('titleKeybinding', "{0} ({1})", this.activity.name, this.keybindingLabel) : this.activity.name;
const badge = (this.getAction() as ActivityAction).getBadge();
if (badge?.getDescription()) {
title = localize('badgeTitle', "{0} - {1}", title, badge.getDescription());
}
return title;
}
private computeKeybindingLabel(): string | undefined | null {
const keybinding = this.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.activity.keybindingId) : null;
return keybinding?.getLabel();
}
private updateHover(): void {
this.hoverDisposables.clear();
this.updateTitle();
if (this.useCustomHover) {
this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_OVER, true)(() => {
if (!this.showHoverScheduler.isScheduled()) {
this.showHoverScheduler.schedule(this.options.hoverOptions!.delay() || 150);
}
}));
this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_LEAVE, true)(() => {
this.hover.value = undefined;
this.showHoverScheduler.cancel();
}));
this.hoverDisposables.add(toDisposable(() => {
this.hover.value = undefined;
this.showHoverScheduler.cancel();
}));
}
}
private showHover(): void {
if (this.hover.value) {
return;
}
const hoverPosition = this.options.hoverOptions!.position();
this.hover.value = this.hoverService.showHover({
target: this.container,
hoverPosition,
text: this.computeTitle(),
showPointer: true,
compact: true
});
}
private get useCustomHover(): boolean {
return !!this.configurationService.getValue<boolean>('workbench.experimental.useCustomHover');
}
override dispose(): void {
super.dispose();
if (this.mouseUpTimeout) {
@@ -375,7 +452,7 @@ export class CompositeOverflowActivityAction extends ActivityAction {
});
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.showMenu();
}
}
@@ -390,10 +467,14 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
private getBadge: (compositeId: string) => IBadge,
private getCompositeOpenAction: (compositeId: string) => IAction,
colors: (theme: IColorTheme) => ICompositeBarColors,
hoverOptions: IActivityHoverOptions,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(action, { icon: true, colors, hasPopup: true }, themeService);
super(action, { icon: true, colors, hasPopup: true, hoverOptions }, themeService, hoverService, configurationService, keybindingService);
}
showMenu(): void {
@@ -434,7 +515,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
});
}
dispose(): void {
override dispose(): void {
super.dispose();
if (this.actions) {
@@ -451,7 +532,7 @@ class ManageExtensionAction extends Action {
super('activitybar.manage.extension', localize('manageExtension', "Manage Extension"));
}
run(id: string): Promise<void> {
override run(id: string): Promise<void> {
return this.commandService.executeCommand('_extensions.manage', id);
}
}
@@ -460,64 +541,29 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
private static manageExtensionAction: ManageExtensionAction;
private compositeActivity: IActivity | undefined;
constructor(
private compositeActivityAction: ActivityAction,
private toggleCompositePinnedAction: IAction,
private compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
private contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
icon: boolean,
private dndHandler: ICompositeDragAndDrop,
private compositeBar: ICompositeBar,
options: IActivityActionViewItemOptions,
private readonly compositeActivityAction: ActivityAction,
private readonly toggleCompositePinnedAction: IAction,
private readonly compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
private readonly contextMenuActionsProvider: () => IAction[],
private readonly dndHandler: ICompositeDragAndDrop,
private readonly compositeBar: ICompositeBar,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IConfigurationService configurationService: IConfigurationService,
) {
super(compositeActivityAction, { draggable: true, colors, icon }, themeService);
super(compositeActivityAction, options, themeService, hoverService, configurationService, keybindingService);
if (!CompositeActionViewItem.manageExtensionAction) {
CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
}
this._register(compositeActivityAction.onDidChangeActivity(() => {
this.compositeActivity = undefined;
this.updateActivity();
}, this));
this._register(Event.any(
compositeActivityAction.onDidChangeActivity,
Event.filter(keybindingService.onDidUpdateKeybindings, () => this.compositeActivity!.name !== this.getActivtyName())
)(() => {
if (this.compositeActivity && this.compositeActivity.name !== this.getActivtyName()) {
this.compositeActivity = undefined;
this.updateActivity();
}
}));
}
protected get activity(): IActivity {
if (!this.compositeActivity) {
this.compositeActivity = {
...this.compositeActivityAction.activity,
... { name: this.getActivtyName() }
};
}
return this.compositeActivity;
}
private getActivtyName(skipKeybinding = false): string {
let name = this.compositeActivityAction.activity.name;
if (skipKeybinding) {
return name;
}
const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null;
return keybinding ? localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name;
}
render(container: HTMLElement): void {
override render(container: HTMLElement): void {
super.render(container);
this.updateChecked();
@@ -627,10 +673,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
const isPinned = this.compositeBar.isPinned(this.activity.id);
if (isPinned) {
this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.getActivtyName(true));
this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.activity.name);
this.toggleCompositePinnedAction.checked = false;
} else {
this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.getActivtyName(true));
this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.activity.name);
}
const otherActions = this.contextMenuActionsProvider();
@@ -652,7 +698,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
});
}
protected updateChecked(): void {
protected override updateChecked(): void {
if (this.getAction().checked) {
this.container.classList.add('checked');
this.container.setAttribute('aria-label', this.container.title);
@@ -667,7 +713,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.updateStyles();
}
protected updateEnabled(): void {
protected override updateEnabled(): void {
if (!this.element) {
return;
}
@@ -679,7 +725,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
}
}
dispose(): void {
override dispose(): void {
super.dispose();
this.label.remove();
}
@@ -696,7 +742,7 @@ export class ToggleCompositePinnedAction extends Action {
this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id);
}
async run(context: string): Promise<void> {
override async run(context: string): Promise<void> {
const id = this.activity ? this.activity.id : context;
if (this.compositeBar.isPinned(id)) {

View File

@@ -75,7 +75,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
protected readonly storageService: IStorageService,
private readonly telemetryService: ITelemetryService,
protected readonly contextMenuService: IContextMenuService,
protected readonly layoutService: IWorkbenchLayoutService,
layoutService: IWorkbenchLayoutService,
protected readonly keybindingService: IKeybindingService,
protected readonly instantiationService: IInstantiationService,
themeService: IThemeService,
@@ -263,9 +263,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
}
// Log in telemetry
if (this.telemetryService) {
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry });
}
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry });
});
// Indicate to composite that it is now visible
@@ -376,7 +374,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return composite;
}
createTitleArea(parent: HTMLElement): HTMLElement {
override createTitleArea(parent: HTMLElement): HTMLElement {
// Title Area Container
const titleArea = append(parent, $('.composite'));
@@ -423,7 +421,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
};
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
// Forward to title label
@@ -451,7 +449,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return null;
}
createContentArea(parent: HTMLElement): HTMLElement {
override createContentArea(parent: HTMLElement): HTMLElement {
const contentContainer = append(parent, $('.content'));
this.progressBar = this._register(new ProgressBar(contentContainer));
@@ -471,7 +469,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return AnchorAlignment.RIGHT;
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
super.layout(width, height);
// Layout contents
@@ -500,7 +498,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return true;
}
dispose(): void {
override dispose(): void {
this.mapCompositeToCompositeContainer.clear();
this.mapActionsBindingToComposite.clear();

View File

@@ -17,6 +17,7 @@ import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogH
import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution {
private readonly model: IDialogsModel;
@@ -30,12 +31,13 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC
@ILayoutService layoutService: ILayoutService,
@IThemeService themeService: IThemeService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IProductService productService: IProductService,
@IClipboardService clipboardService: IClipboardService
) {
super();
this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService);
this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, instantiationService, productService, clipboardService);
this.model = (this.dialogService as DialogService).model;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler } from 'vs/platform/dialogs/common/dialogs';
import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler, ICustomDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { ILogService } from 'vs/platform/log/common/log';
import Severity from 'vs/base/common/severity';
@@ -18,6 +18,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IProductService } from 'vs/platform/product/common/productService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { fromNow } from 'vs/base/common/date';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
export class BrowserDialogHandler implements IDialogHandler {
@@ -30,14 +32,19 @@ export class BrowserDialogHandler implements IDialogHandler {
'editor.action.clipboardPasteAction'
];
private readonly markdownRenderer: MarkdownRenderer;
constructor(
@ILogService private readonly logService: ILogService,
@ILayoutService private readonly layoutService: ILayoutService,
@IThemeService private readonly themeService: IThemeService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IProductService private readonly productService: IProductService,
@IClipboardService private readonly clipboardService: IClipboardService
) { }
) {
this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {});
}
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
this.logService.trace('DialogService#confirm', confirmation.message);
@@ -67,7 +74,7 @@ export class BrowserDialogHandler implements IDialogHandler {
async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<IShowResult> {
this.logService.trace('DialogService#show', message);
const result = await this.doShow(this.getDialogType(severity), message, buttons, options?.detail, options?.cancelId, options?.checkbox);
const result = await this.doShow(this.getDialogType(severity), message, buttons, options?.detail, options?.cancelId, options?.checkbox, undefined, typeof options?.custom === 'object' ? options.custom : undefined);
return {
choice: result.button,
@@ -75,8 +82,19 @@ export class BrowserDialogHandler implements IDialogHandler {
};
}
private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[]): Promise<IDialogResult> {
private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[], customOptions?: ICustomDialogOptions): Promise<IDialogResult> {
const dialogDisposables = new DisposableStore();
const renderBody = customOptions ? (parent: HTMLElement) => {
parent.classList.add(...(customOptions.classes || []));
(customOptions.markdownDetails || []).forEach(markdownDetail => {
const result = this.markdownRenderer.render(markdownDetail.markdown);
parent.appendChild(result.element);
result.element.classList.add(...(markdownDetail.classes || []));
dialogDisposables.add(result);
});
} : undefined;
const dialog = new Dialog(
this.layoutService.container,
message,
@@ -93,6 +111,10 @@ export class BrowserDialogHandler implements IDialogHandler {
}
}
},
renderBody,
icon: customOptions?.icon,
disableCloseAction: customOptions?.disableCloseAction,
buttonDetails: customOptions?.buttonDetails,
checkboxLabel: checkbox?.label,
checkboxChecked: checkbox?.checked,
inputs

View File

@@ -17,7 +17,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
*/
export class BinaryResourceDiffEditor extends SideBySideEditor {
static readonly ID = BINARY_DIFF_EDITOR_ID;
static override readonly ID = BINARY_DIFF_EDITOR_ID;
constructor(
@ITelemetryService telemetryService: ITelemetryService,

View File

@@ -13,18 +13,15 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { URI } from 'vs/base/common/uri';
import { Dimension, size, clearNode, append, addDisposableListener, EventType, $ } from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
import { ByteSize } from 'vs/platform/files/common/files';
export interface IOpenCallbacks {
openInternal: (input: EditorInput, options: EditorOptions | undefined) => Promise<void>;
openExternal: (uri: URI) => void;
}
/*
@@ -42,22 +39,21 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
private metadata: string | undefined;
private binaryContainer: HTMLElement | undefined;
private scrollbar: DomScrollableElement | undefined;
private resourceViewerContext: ResourceViewerContext | undefined;
private inputDisposable = this._register(new MutableDisposable());
constructor(
id: string,
callbacks: IOpenCallbacks,
telemetryService: ITelemetryService,
themeService: IThemeService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IStorageService storageService: IStorageService,
@IStorageService storageService: IStorageService
) {
super(id, telemetryService, themeService, storageService);
this.callbacks = callbacks;
}
getTitle(): string {
override getTitle(): string {
return this.input ? this.input.getName() : localize('binaryEditor', "Binary Viewer");
}
@@ -65,7 +61,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
// Container for Binary
this.binaryContainer = document.createElement('div');
this.binaryContainer.className = 'binary-container';
this.binaryContainer.className = 'monaco-binary-resource-editor';
this.binaryContainer.style.outline = 'none';
this.binaryContainer.tabIndex = 0; // enable focus support from the editor part (do not remove)
@@ -74,7 +70,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
parent.appendChild(this.scrollbar.getDomNode());
}
async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
await super.setInput(input, options, context, token);
const model = await input.resolve();
@@ -89,23 +85,38 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
}
// Render Input
if (this.resourceViewerContext) {
this.resourceViewerContext.dispose();
}
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.resource, size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, {
openInternalClb: () => this.handleOpenInternalCallback(input, options),
openExternalClb: this.environmentService.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource),
metadataClb: meta => this.handleMetadataChanged(meta)
});
this.inputDisposable.value = this.renderInput(input, options, model);
}
private async handleOpenInternalCallback(input: EditorInput, options: EditorOptions | undefined): Promise<void> {
await this.callbacks.openInternal(input, options);
private renderInput(input: EditorInput, options: EditorOptions | undefined, model: BinaryEditorModel): IDisposable {
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
// Signal to listeners that the binary editor has been opened in-place
this._onDidOpenInPlace.fire();
clearNode(binaryContainer);
const disposables = new DisposableStore();
const label = document.createElement('p');
label.textContent = localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.");
binaryContainer.appendChild(label);
const link = append(label, $('a.embedded-link'));
link.setAttribute('role', 'button');
link.textContent = localize('openAsText', "Do you want to open it anyway?");
disposables.add(addDisposableListener(link, EventType.CLICK, async () => {
await this.callbacks.openInternal(input, options);
// Signal to listeners that the binary editor has been opened in-place
this._onDidOpenInPlace.fire();
}));
scrollbar.scanDomNode();
// Update metadata
const size = model.getSize();
this.handleMetadataChanged(typeof size === 'number' ? ByteSize.formatSize(size) : '');
return disposables;
}
private handleMetadataChanged(meta: string | undefined): void {
@@ -118,7 +129,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
return this.metadata;
}
clearInput(): void {
override clearInput(): void {
// Clear Meta
this.handleMetadataChanged(undefined);
@@ -127,8 +138,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
if (this.binaryContainer) {
clearNode(this.binaryContainer);
}
dispose(this.resourceViewerContext);
this.resourceViewerContext = undefined;
this.inputDisposable.clear();
super.clearInput();
}
@@ -139,118 +149,17 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
size(binaryContainer, dimension.width, dimension.height);
scrollbar.scanDomNode();
if (typeof this.resourceViewerContext?.layout === 'function') {
this.resourceViewerContext.layout(dimension);
}
}
focus(): void {
override focus(): void {
const binaryContainer = assertIsDefined(this.binaryContainer);
binaryContainer.focus();
}
dispose(): void {
if (this.binaryContainer) {
this.binaryContainer.remove();
}
dispose(this.resourceViewerContext);
this.resourceViewerContext = undefined;
override dispose(): void {
this.binaryContainer?.remove();
super.dispose();
}
}
export interface IResourceDescriptor {
readonly resource: URI;
readonly name: string;
readonly size?: number;
readonly etag?: string;
readonly mime: string;
}
interface ResourceViewerContext extends IDisposable {
layout?(dimension: Dimension): void;
}
interface ResourceViewerDelegate {
openInternalClb(uri: URI): void;
openExternalClb?(uri: URI): void;
metadataClb(meta: string): void;
}
class ResourceViewer {
private static readonly MAX_OPEN_INTERNAL_SIZE = ByteSize.MB * 200; // max size until we offer an action to open internally
static show(
descriptor: IResourceDescriptor,
container: HTMLElement,
scrollbar: DomScrollableElement,
delegate: ResourceViewerDelegate,
): ResourceViewerContext {
// Ensure CSS class
container.className = 'monaco-binary-resource-editor';
// Large Files
if (typeof descriptor.size === 'number' && descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) {
return FileTooLargeFileView.create(container, descriptor.size, scrollbar, delegate);
}
// Seemingly Binary Files
return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, delegate);
}
}
class FileTooLargeFileView {
static create(
container: HTMLElement,
descriptorSize: number,
scrollbar: DomScrollableElement,
delegate: ResourceViewerDelegate
) {
const size = ByteSize.formatSize(descriptorSize);
delegate.metadataClb(size);
clearNode(container);
const label = document.createElement('span');
label.textContent = localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size);
container.appendChild(label);
scrollbar.scanDomNode();
return Disposable.None;
}
}
class FileSeemsBinaryFileView {
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
scrollbar: DomScrollableElement,
delegate: ResourceViewerDelegate
) {
delegate.metadataClb(typeof descriptor.size === 'number' ? ByteSize.formatSize(descriptor.size) : '');
clearNode(container);
const disposables = new DisposableStore();
const label = document.createElement('p');
label.textContent = localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.");
container.appendChild(label);
const link = append(label, $('a.embedded-link'));
link.setAttribute('role', 'button');
link.textContent = localize('openAsText', "Do you want to open it anyway?");
disposables.add(addDisposableListener(link, EventType.CLICK, () => delegate.openInternalClb(descriptor.resource)));
scrollbar.scanDomNode();
return disposables;
}
}

View File

@@ -54,7 +54,7 @@ class OutlineItem extends BreadcrumbsItem {
super();
}
dispose(): void {
override dispose(): void {
this._disposables.dispose();
}
@@ -114,7 +114,7 @@ class FileItem extends BreadcrumbsItem {
super();
}
dispose(): void {
override dispose(): void {
this._disposables.dispose();
}
@@ -147,6 +147,7 @@ export interface IBreadcrumbsControlOptions {
showSymbolIcons: boolean;
showDecorationColors: boolean;
breadcrumbsBackground: ColorIdentifier | ColorFunction;
showPlaceholder: boolean;
}
export class BreadcrumbsControl {
@@ -288,8 +289,21 @@ export class BreadcrumbsControl {
showSymbolIcons: this._options.showSymbolIcons && showIcons
};
const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options));
this._widget.setItems(items);
this._widget.reveal(items[items.length - 1]);
if (items.length === 0) {
this._widget.setEnabled(false);
this._widget.setItems([new class extends BreadcrumbsItem {
render(container: HTMLElement): void {
container.innerText = localize('empty', "no elements");
}
equals(other: BreadcrumbsItem): boolean {
return other === this;
}
}]);
} else {
this._widget.setEnabled(true);
this._widget.setItems(items);
this._widget.reveal(items[items.length - 1]);
}
};
const listener = model.onDidUpdate(updateBreadcrumbs);
const configListener = this._cfShowIcons.onDidChange(updateBreadcrumbs);

View File

@@ -348,7 +348,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
constructor(
parent: HTMLElement,
protected resource: URI,
resource: URI,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IConfigurationService configService: IConfigurationService,

View File

@@ -6,8 +6,8 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { localize } from 'vs/nls';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext } from 'vs/workbench/common/editor';
import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor';
import { EditorInput, IEditorInputSerializer, SideBySideEditorInput, IEditorInputFactoryRegistry, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, EditorExtensions } from 'vs/workbench/common/editor';
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
@@ -118,8 +118,8 @@ interface ISerializedUntitledTextEditorInput {
encoding: string | undefined;
}
// Register Editor Input Factory
class UntitledTextEditorInputFactory implements IEditorInputFactory {
// Register Editor Input Serializer
class UntitledTextEditorInputSerializer implements IEditorInputSerializer {
constructor(
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@@ -136,7 +136,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory {
return undefined;
}
const untitledTextEditorInput = <UntitledTextEditorInput>editorInput;
const untitledTextEditorInput = editorInput as UntitledTextEditorInput;
let resource = untitledTextEditorInput.resource;
if (untitledTextEditorInput.model.hasAssociatedFilePath) {
@@ -165,7 +165,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory {
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledTextEditorInput {
return instantiationService.invokeFunction<UntitledTextEditorInput>(accessor => {
return instantiationService.invokeFunction(accessor => {
const deserialized: ISerializedUntitledTextEditorInput = JSON.parse(serializedEditorInput);
const resource = URI.revive(deserialized.resourceJSON);
const mode = deserialized.modeId;
@@ -176,9 +176,9 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory {
}
}
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledTextEditorInput.ID, UntitledTextEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(UntitledTextEditorInput.ID, UntitledTextEditorInputSerializer);
// Register SideBySide/DiffEditor Input Factory
// Register SideBySide/DiffEditor Input Serializer
interface ISerializedSideBySideEditorInput {
name: string;
description: string | undefined;
@@ -190,21 +190,21 @@ interface ISerializedSideBySideEditorInput {
secondaryTypeId: string;
}
export abstract class AbstractSideBySideEditorInputFactory implements IEditorInputFactory {
export abstract class AbstractSideBySideEditorInputSerializer implements IEditorInputSerializer {
private getInputFactories(secondaryId: string, primaryId: string): [IEditorInputFactory | undefined, IEditorInputFactory | undefined] {
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
private getInputSerializers(secondaryEditorInputTypeId: string, primaryEditorInputTypeId: string): [IEditorInputSerializer | undefined, IEditorInputSerializer | undefined] {
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
return [registry.getEditorInputFactory(secondaryId), registry.getEditorInputFactory(primaryId)];
return [registry.getEditorInputSerializer(secondaryEditorInputTypeId), registry.getEditorInputSerializer(primaryEditorInputTypeId)];
}
canSerialize(editorInput: EditorInput): boolean {
const input = editorInput as SideBySideEditorInput | DiffEditorInput;
if (input.primary && input.secondary) {
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId());
const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(input.secondary.typeId, input.primary.typeId);
return !!(secondaryInputFactory?.canSerialize(input.secondary) && primaryInputFactory?.canSerialize(input.primary));
return !!(secondaryInputSerializer?.canSerialize(input.secondary) && primaryInputSerializer?.canSerialize(input.primary));
}
return false;
@@ -214,10 +214,10 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
const input = editorInput as SideBySideEditorInput | DiffEditorInput;
if (input.primary && input.secondary) {
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId());
if (primaryInputFactory && secondaryInputFactory) {
const primarySerialized = primaryInputFactory.serialize(input.primary);
const secondarySerialized = secondaryInputFactory.serialize(input.secondary);
const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(input.secondary.typeId, input.primary.typeId);
if (primaryInputSerializer && secondaryInputSerializer) {
const primarySerialized = primaryInputSerializer.serialize(input.primary);
const secondarySerialized = secondaryInputSerializer.serialize(input.secondary);
if (primarySerialized && secondarySerialized) {
const serializedEditorInput: ISerializedSideBySideEditorInput = {
@@ -225,8 +225,8 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
description: input.getDescription(),
primarySerialized: primarySerialized,
secondarySerialized: secondarySerialized,
primaryTypeId: input.primary.getTypeId(),
secondaryTypeId: input.secondary.getTypeId()
primaryTypeId: input.primary.typeId,
secondaryTypeId: input.secondary.typeId
};
return JSON.stringify(serializedEditorInput);
@@ -240,10 +240,10 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined {
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(deserialized.secondaryTypeId, deserialized.primaryTypeId);
if (primaryInputFactory && secondaryInputFactory) {
const primaryInput = primaryInputFactory.deserialize(instantiationService, deserialized.primarySerialized);
const secondaryInput = secondaryInputFactory.deserialize(instantiationService, deserialized.secondarySerialized);
const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(deserialized.secondaryTypeId, deserialized.primaryTypeId);
if (primaryInputSerializer && secondaryInputSerializer) {
const primaryInput = primaryInputSerializer.deserialize(instantiationService, deserialized.primarySerialized);
const secondaryInput = secondaryInputSerializer.deserialize(instantiationService, deserialized.secondarySerialized);
if (primaryInput && secondaryInput) {
return this.createEditorInput(instantiationService, deserialized.name, deserialized.description, secondaryInput, primaryInput);
@@ -256,22 +256,22 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
protected abstract createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput;
}
class SideBySideEditorInputFactory extends AbstractSideBySideEditorInputFactory {
class SideBySideEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {
protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
return new SideBySideEditorInput(name, description, secondaryInput, primaryInput);
}
}
class DiffEditorInputFactory extends AbstractSideBySideEditorInputFactory {
class DiffEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {
protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
return instantiationService.createInstance(DiffEditorInput, name, description, secondaryInput, primaryInput, undefined);
}
}
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(DiffEditorInput.ID, DiffEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(SideBySideEditorInput.ID, SideBySideEditorInputSerializer);
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(DiffEditorInput.ID, DiffEditorInputSerializer);
// Register Editor Contributions
registerEditorContribution(OpenWorkspaceButtonContribution.ID, OpenWorkspaceButtonContribution);

Some files were not shown because too many files have changed in this diff Show More