diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index bdcc94c864e..6ea3c01a77c 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -8,7 +8,7 @@ import { disposed } from 'vs/base/common/errors'; import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { equals as objectEquals } from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; +import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; @@ -20,7 +20,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; -import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, IWorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, IWorkspaceEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -29,6 +29,23 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { revive } from 'vs/base/common/marshalling'; + +function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] { + if (!data?.edits) { + return []; + } + + const result: ResourceEdit[] = []; + for (let edit of revive(data).edits) { + if (edit._type === WorkspaceEditType.File) { + result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata)); + } else if (edit._type === WorkspaceEditType.Text) { + result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata)); + } + } + return result; +} export class MainThreadTextEditors implements MainThreadTextEditorsShape { @@ -222,8 +239,8 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { - const { edits } = reviveWorkspaceEditDto(dto)!; - return this._bulkEditService.apply(ResourceEdit.convert({ edits })).then(() => true, _err => false); + const edits = reviveWorkspaceEditDto2(dto); + return this._bulkEditService.apply(edits).then(() => true, _err => false); } $tryInsertSnippet(id: string, template: string, ranges: readonly IRange[], opts: IUndoStopOptions): Promise { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e09f81490e8..e0415cf4584 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1232,7 +1232,13 @@ export interface IWorkspaceEditEntryMetadataDto { iconPath?: { id: string } | UriComponents | { light: UriComponents, dark: UriComponents }; } +export const enum WorkspaceEditType { + File = 1, + Text = 2, +} + export interface IWorkspaceFileEditDto { + _type: WorkspaceEditType.File; oldUri?: UriComponents; newUri?: UriComponents; options?: modes.WorkspaceFileEditOptions @@ -1240,6 +1246,7 @@ export interface IWorkspaceFileEditDto { } export interface IWorkspaceTextEditDto { + _type: WorkspaceEditType.Text; resource: UriComponents; edit: modes.TextEdit; modelVersionId?: number; diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 83fc7bc31ac..8dc69cb7abd 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -141,7 +141,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { try { const result = await this._proxy.$executeCommand(id, toArgs, retry); - return revive(result); + return revive(result); } catch (e) { // Rerun the command when it wasn't known, had arguments, and when retry // is enabled. We do this because the command might be registered inside diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index 1eb5b7bc391..5613e66db18 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { illegalState } from 'vs/base/common/errors'; -import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IWorkspaceEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -146,6 +146,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic if (Array.isArray(value) && (value).every(e => e instanceof TextEdit)) { for (const { newText, newEol, range } of value) { dto.edits.push({ + _type: WorkspaceEditType.Text, resource: document.uri, edit: { range: range && Range.from(range), diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 66b48dbacd8..c5712ebba6d 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -513,9 +513,10 @@ export namespace WorkspaceEdit { if (value instanceof types.WorkspaceEdit) { for (let entry of value.allEntries()) { - if (entry._type === 1) { + if (entry._type === types.FileEditType.File) { // file operation result.edits.push({ + _type: extHostProtocol.WorkspaceEditType.File, oldUri: entry.from, newUri: entry.to, options: entry.options, @@ -526,6 +527,7 @@ export namespace WorkspaceEdit { // text edits const doc = documents?.getDocument(entry.uri); result.edits.push({ + _type: extHostProtocol.WorkspaceEditType.Text, resource: entry.uri, edit: TextEdit.from(entry.edit), modelVersionId: doc?.version, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5d6b6dc15a2..1ab689c1850 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, equals } from 'vs/base/common/arrays'; +import { coalesceInPlace, equals } from 'vs/base/common/arrays'; import { escapeCodicons } from 'vs/base/common/codicons'; import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; import { isMarkdownString } from 'vs/base/common/htmlContent'; +import { ResourceMap } from 'vs/base/common/map'; import { startsWith } from 'vs/base/common/strings'; import { isStringArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -575,8 +576,13 @@ export interface IFileOperationOptions { recursive?: boolean; } +export const enum FileEditType { + File = 1, + Text = 2 +} + export interface IFileOperation { - _type: 1; + _type: FileEditType.File; from?: URI; to?: URI; options?: IFileOperationOptions; @@ -584,7 +590,7 @@ export interface IFileOperation { } export interface IFileTextEdit { - _type: 2; + _type: FileEditType.Text; uri: URI; edit: TextEdit; metadata?: vscode.WorkspaceEditEntryMetadata; @@ -593,22 +599,31 @@ export interface IFileTextEdit { @es5ClassCompat export class WorkspaceEdit implements vscode.WorkspaceEdit { - private _edits = new Array(); + private readonly _edits = new Array(); + + + allEntries(): ReadonlyArray { + return this._edits; + } + + // --- file renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ _type: 1, from, to, options, metadata }); + this._edits.push({ _type: FileEditType.File, from, to, options, metadata }); } createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ _type: 1, from: undefined, to: uri, options, metadata }); + this._edits.push({ _type: FileEditType.File, from: undefined, to: uri, options, metadata }); } deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ _type: 1, from: uri, to: undefined, options, metadata }); + this._edits.push({ _type: FileEditType.File, from: uri, to: undefined, options, metadata }); } + // --- text + replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ _type: 2, uri, edit: new TextEdit(range, newText), metadata }); + this._edits.push({ _type: FileEditType.Text, uri, edit: new TextEdit(range, newText), metadata }); } insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void { @@ -619,8 +634,10 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { this.replace(resource, range, '', metadata); } + // --- text (Maplike) + has(uri: URI): boolean { - return this._edits.some(edit => edit._type === 2 && edit.uri.toString() === uri.toString()); + return this._edits.some(edit => edit._type === FileEditType.Text && edit.uri.toString() === uri.toString()); } set(uri: URI, edits: TextEdit[]): void { @@ -628,16 +645,16 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { // remove all text edits for `uri` for (let i = 0; i < this._edits.length; i++) { const element = this._edits[i]; - if (element._type === 2 && element.uri.toString() === uri.toString()) { + if (element._type === FileEditType.Text && element.uri.toString() === uri.toString()) { this._edits[i] = undefined!; // will be coalesced down below } } - this._edits = coalesce(this._edits); + coalesceInPlace(this._edits); } else { // append edit to the end for (const edit of edits) { if (edit) { - this._edits.push({ _type: 2, uri, edit }); + this._edits.push({ _type: FileEditType.Text, uri, edit }); } } } @@ -646,7 +663,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { get(uri: URI): TextEdit[] { const res: TextEdit[] = []; for (let candidate of this._edits) { - if (candidate._type === 2 && candidate.uri.toString() === uri.toString()) { + if (candidate._type === FileEditType.Text && candidate.uri.toString() === uri.toString()) { res.push(candidate.edit); } } @@ -654,13 +671,13 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } entries(): [URI, TextEdit[]][] { - const textEdits = new Map(); + const textEdits = new ResourceMap<[URI, TextEdit[]]>(); for (let candidate of this._edits) { - if (candidate._type === 2) { - let textEdit = textEdits.get(candidate.uri.toString()); + if (candidate._type === FileEditType.Text) { + let textEdit = textEdits.get(candidate.uri); if (!textEdit) { textEdit = [candidate.uri, []]; - textEdits.set(candidate.uri.toString(), textEdit); + textEdits.set(candidate.uri, textEdit); } textEdit[1].push(candidate.edit); } @@ -668,22 +685,6 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { return [...textEdits.values()]; } - allEntries(): ReadonlyArray { - return this._edits; - } - - // _allEntries(): ([URI, TextEdit] | [URI?, URI?, IFileOperationOptions?])[] { - // const res: ([URI, TextEdit] | [URI?, URI?, IFileOperationOptions?])[] = []; - // for (let edit of this._edits) { - // if (edit._type === 1) { - // res.push([edit.from, edit.to, edit.options]); - // } else { - // res.push([edit.uri, edit.edit]); - // } - // } - // return res; - // } - get size(): number { return this.entries().length; } diff --git a/src/vs/workbench/test/browser/api/extHostTextEditors.test.ts b/src/vs/workbench/test/browser/api/extHostTextEditors.test.ts index 4d4a90b1ec1..0d94ec01908 100644 --- a/src/vs/workbench/test/browser/api/extHostTextEditors.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTextEditors.test.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { MainContext, MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, MainThreadTextEditorsShape, IWorkspaceEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; -import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { NullLogService } from 'vs/platform/log/common/log'; +import { assertType } from 'vs/base/common/types'; suite('ExtHostTextEditors.applyWorkspaceEdit', () => { @@ -48,7 +48,9 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { edit.replace(resource, new extHostTypes.Range(0, 0, 0, 0), 'hello'); await editors.applyWorkspaceEdit(edit); assert.equal(workspaceResourceEdits.edits.length, 1); - assert.equal((workspaceResourceEdits.edits[0]).modelVersionId, 1337); + const [first] = workspaceResourceEdits.edits; + assertType(first._type === WorkspaceEditType.Text); + assert.equal(first.modelVersionId, 1337); }); test('does not use version id if document is not available', async () => { @@ -56,7 +58,9 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { edit.replace(URI.parse('foo:bar2'), new extHostTypes.Range(0, 0, 0, 0), 'hello'); await editors.applyWorkspaceEdit(edit); assert.equal(workspaceResourceEdits.edits.length, 1); - assert.ok(typeof (workspaceResourceEdits.edits[0]).modelVersionId === 'undefined'); + const [first] = workspaceResourceEdits.edits; + assertType(first._type === WorkspaceEditType.Text); + assert.ok(typeof first.modelVersionId === 'undefined'); }); }); diff --git a/src/vs/workbench/test/browser/api/extHostTypes.test.ts b/src/vs/workbench/test/browser/api/extHostTypes.test.ts index 92e9616c1a8..fe6998e08c6 100644 --- a/src/vs/workbench/test/browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTypes.test.ts @@ -388,17 +388,17 @@ suite('ExtHostTypes', function () { assert.equal(all.length, 4); const [first, second, third, fourth] = all; - assertType(first._type === 2); + assertType(first._type === types.FileEditType.Text); assert.equal(first.uri.toString(), 'foo:a'); - assertType(second._type === 1); + assertType(second._type === types.FileEditType.File); assert.equal(second.from!.toString(), 'foo:a'); assert.equal(second.to!.toString(), 'foo:b'); - assertType(third._type === 2); + assertType(third._type === types.FileEditType.Text); assert.equal(third.uri.toString(), 'foo:a'); - assertType(fourth._type === 2); + assertType(fourth._type === types.FileEditType.Text); assert.equal(fourth.uri.toString(), 'foo:b'); }); @@ -411,8 +411,8 @@ suite('ExtHostTypes', function () { assert.equal(edit.allEntries().length, 2); let [first, second] = edit.allEntries(); - assertType(first._type === 2); - assertType(second._type === 2); + assertType(first._type === types.FileEditType.Text); + assertType(second._type === types.FileEditType.Text); assert.equal(first.edit.newText, 'Hello'); assert.equal(second.edit.newText, 'Foo'); }); diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index f4d82ca1786..b413ec9fa5d 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -10,7 +10,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ 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 } from 'vs/workbench/api/common/extHost.protocol'; +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'; @@ -170,6 +170,7 @@ suite('MainThreadEditors', () => { let model = modelService.createModel('something', null, resource); let workspaceResourceEdit: IWorkspaceTextEditDto = { + _type: WorkspaceEditType.Text, resource: resource, modelVersionId: model.getVersionId(), edit: { @@ -191,6 +192,7 @@ suite('MainThreadEditors', () => { let model = modelService.createModel('something', null, resource); let workspaceResourceEdit1: IWorkspaceTextEditDto = { + _type: WorkspaceEditType.Text, resource: resource, modelVersionId: model.getVersionId(), edit: { @@ -199,6 +201,7 @@ suite('MainThreadEditors', () => { } }; let workspaceResourceEdit2: IWorkspaceTextEditDto = { + _type: WorkspaceEditType.Text, resource: resource, modelVersionId: model.getVersionId(), edit: { @@ -221,9 +224,9 @@ suite('MainThreadEditors', () => { test(`applyWorkspaceEdit with only resource edit`, () => { return editors.$tryApplyWorkspaceEdit({ edits: [ - { oldUri: resource, newUri: resource, options: undefined }, - { oldUri: undefined, newUri: resource, options: undefined }, - { oldUri: resource, newUri: undefined, options: undefined } + { _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.equal(result, true);