/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { SingleProxyRPCProtocol, TestRPCProtocol } from './testRPCProtocol'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ExtHostDocumentsAndEditorsShape, ExtHostContext, ExtHostDocumentsShape, IWorkspaceTextEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; import { mock } from 'vs/base/test/common/mock'; import { Event } from 'vs/base/common/event'; import { MainThreadTextEditors } from 'vs/workbench/api/browser/mainThreadEditors'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { IModelService } from 'vs/editor/common/services/modelService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { BulkEditService } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditService'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IReference, ImmortalReference } from 'vs/base/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkingCopyFileService, IMoveOperation, IDeleteOperation, ICopyOperation, ICreateFileOperation, ICreateOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { extUri } from 'vs/base/common/resources'; import { ITextSnapshot } from 'vs/editor/common/model'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; suite('MainThreadEditors', () => { const resource = URI.parse('foo:bar'); let modelService: IModelService; let editors: MainThreadTextEditors; const movedResources = new Map(); const copiedResources = new Map(); const createdResources = new Set(); const deletedResources = new Set(); setup(() => { movedResources.clear(); copiedResources.clear(); createdResources.clear(); deletedResources.clear(); const configService = new TestConfigurationService(); const dialogService = new TestDialogService(); const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); const services = new ServiceCollection(); services.set(IBulkEditService, new SyncDescriptor(BulkEditService)); services.set(ILabelService, new SyncDescriptor(LabelService)); services.set(ILogService, new NullLogService()); services.set(IWorkspaceContextService, new TestContextService()); services.set(IWorkbenchEnvironmentService, TestEnvironmentService); services.set(IConfigurationService, configService); services.set(IDialogService, dialogService); services.set(INotificationService, notificationService); services.set(IUndoRedoService, undoRedoService); services.set(IModelService, modelService); services.set(ICodeEditorService, new TestCodeEditorService()); services.set(IFileService, new TestFileService()); services.set(IEditorService, new TestEditorService()); services.set(ILifecycleService, new TestLifecycleService()); services.set(IEditorGroupsService, new TestEditorGroupsService()); services.set(ITextFileService, new class extends mock() { isDirty() { return false; } files = { onDidSave: Event.None, onDidRevert: Event.None, onDidChangeDirty: Event.None }; create(operations: { resource: URI }[]) { for (const o of operations) { createdResources.add(o.resource); } return Promise.resolve(Object.create(null)); } async getEncodedReadable(resource: URI, value?: string | ITextSnapshot): Promise { return undefined; } }); services.set(IWorkingCopyFileService, new class extends mock() { onDidRunWorkingCopyFileOperation = Event.None; createFolder(operations: ICreateOperation[]): any { this.create(operations); } create(operations: ICreateFileOperation[]) { for (const operation of operations) { createdResources.add(operation.resource); } return Promise.resolve(Object.create(null)); } move(operations: IMoveOperation[]) { const { source, target } = operations[0].file; movedResources.set(source, target); return Promise.resolve(Object.create(null)); } copy(operations: ICopyOperation[]) { const { source, target } = operations[0].file; copiedResources.set(source, target); return Promise.resolve(Object.create(null)); } delete(operations: IDeleteOperation[]) { for (const operation of operations) { deletedResources.add(operation.resource); } return Promise.resolve(undefined); } }); services.set(ITextModelService, new class extends mock() { createModelReference(resource: URI): Promise> { const textEditorModel = new class extends mock() { textEditorModel = modelService.getModel(resource)!; }; textEditorModel.isReadonly = () => false; return Promise.resolve(new ImmortalReference(textEditorModel)); } }); services.set(IEditorWorkerService, new class extends mock() { }); services.set(IPanelService, new class extends mock() implements IPanelService { declare readonly _serviceBrand: undefined; onDidPanelOpen = Event.None; onDidPanelClose = Event.None; getActivePanel() { return undefined; } }); services.set(IUriIdentityService, new class extends mock() { get extUri() { return extUri; } }); const instaService = new InstantiationService(services); const rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock() { $acceptModelChanged(): void { } }); rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new class extends mock() { $acceptDocumentsAndEditorsDelta(): void { } }); const documentAndEditor = instaService.createInstance(MainThreadDocumentsAndEditors, rpcProtocol); editors = instaService.createInstance(MainThreadTextEditors, documentAndEditor, SingleProxyRPCProtocol(null)); }); test(`applyWorkspaceEdit returns false if model is changed by user`, () => { let model = modelService.createModel('something', null, resource); let workspaceResourceEdit: IWorkspaceTextEditDto = { _type: WorkspaceEditType.Text, resource: resource, modelVersionId: model.getVersionId(), edit: { text: 'asdfg', range: new Range(1, 1, 1, 1) } }; // Act as if the user edited the model model.applyEdits([EditOperation.insert(new Position(0, 0), 'something')]); return editors.$tryApplyWorkspaceEdit({ edits: [workspaceResourceEdit] }).then((result) => { assert.strictEqual(result, false); }); }); test(`issue #54773: applyWorkspaceEdit checks model version in race situation`, () => { let model = modelService.createModel('something', null, resource); let workspaceResourceEdit1: IWorkspaceTextEditDto = { _type: WorkspaceEditType.Text, resource: resource, modelVersionId: model.getVersionId(), edit: { text: 'asdfg', range: new Range(1, 1, 1, 1) } }; let workspaceResourceEdit2: IWorkspaceTextEditDto = { _type: WorkspaceEditType.Text, resource: resource, modelVersionId: model.getVersionId(), edit: { text: 'asdfg', range: new Range(1, 1, 1, 1) } }; let p1 = editors.$tryApplyWorkspaceEdit({ edits: [workspaceResourceEdit1] }).then((result) => { // first edit request succeeds assert.strictEqual(result, true); }); let p2 = editors.$tryApplyWorkspaceEdit({ edits: [workspaceResourceEdit2] }).then((result) => { // second edit request fails assert.strictEqual(result, false); }); return Promise.all([p1, p2]); }); test(`applyWorkspaceEdit with only resource edit`, () => { return editors.$tryApplyWorkspaceEdit({ edits: [ { _type: WorkspaceEditType.File, oldUri: resource, newUri: resource, options: undefined }, { _type: WorkspaceEditType.File, oldUri: undefined, newUri: resource, options: undefined }, { _type: WorkspaceEditType.File, oldUri: resource, newUri: undefined, options: undefined } ] }).then((result) => { assert.strictEqual(result, true); assert.strictEqual(movedResources.get(resource), resource); assert.strictEqual(createdResources.has(resource), true); assert.strictEqual(deletedResources.has(resource), true); }); }); });