Skip to content

Commit

Permalink
refactor bulk edit service a little so that it is fit for new edit ty…
Browse files Browse the repository at this point in the history
…pes, #105283
  • Loading branch information
jrieken committed Aug 26, 2020
1 parent 466cc0f commit 0681925
Show file tree
Hide file tree
Showing 18 changed files with 297 additions and 293 deletions.
58 changes: 54 additions & 4 deletions src/vs/editor/browser/services/bulkEditService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,64 @@
*--------------------------------------------------------------------------------------------*/

import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { WorkspaceEdit } from 'vs/editor/common/modes';
import { TextEdit, WorkspaceEdit, WorkspaceEditMetadata, WorkspaceFileEdit, WorkspaceFileEditOptions, WorkspaceTextEdit } from 'vs/editor/common/modes';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { isObject } from 'vs/base/common/types';

export const IBulkEditService = createDecorator<IBulkEditService>('IWorkspaceEditService');

function isWorkspaceFileEdit(thing: any): thing is WorkspaceFileEdit {
return isObject(thing) && (Boolean((<WorkspaceFileEdit>thing).newUri) || Boolean((<WorkspaceFileEdit>thing).oldUri));
}

function isWorkspaceTextEdit(thing: any): thing is WorkspaceTextEdit {
return isObject(thing) && URI.isUri((<WorkspaceTextEdit>thing).resource) && isObject((<WorkspaceTextEdit>thing).edit);
}

export class ResourceEdit {

protected constructor(readonly metadata?: WorkspaceEditMetadata) { }

static convert(edit: WorkspaceEdit): ResourceEdit[] {


return edit.edits.map(edit => {
if (isWorkspaceTextEdit(edit)) {
return new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata);
}
if (isWorkspaceFileEdit(edit)) {
return new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata);
}
throw new Error('Unsupported edit');
});
}
}

export class ResourceTextEdit extends ResourceEdit {
constructor(
readonly resource: URI,
readonly textEdit: TextEdit,
readonly versionId?: number,
readonly metadata?: WorkspaceEditMetadata
) {
super(metadata);
}
}

export class ResourceFileEdit extends ResourceEdit {
constructor(
readonly oldResource: URI | undefined,
readonly newResource: URI | undefined,
readonly options?: WorkspaceFileEditOptions,
readonly metadata?: WorkspaceEditMetadata
) {
super(metadata);
}
}

export interface IBulkEditOptions {
editor?: ICodeEditor;
progress?: IProgress<IProgressStep>;
Expand All @@ -23,7 +74,7 @@ export interface IBulkEditResult {
ariaSummary: string;
}

export type IBulkEditPreviewHandler = (edit: WorkspaceEdit, options?: IBulkEditOptions) => Promise<WorkspaceEdit>;
export type IBulkEditPreviewHandler = (edits: ResourceEdit[], options?: IBulkEditOptions) => Promise<ResourceEdit[]>;

export interface IBulkEditService {
readonly _serviceBrand: undefined;
Expand All @@ -32,6 +83,5 @@ export interface IBulkEditService {

setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable;

apply(edit: WorkspaceEdit, options?: IBulkEditOptions): Promise<IBulkEditResult>;
apply(edit: ResourceEdit[], options?: IBulkEditOptions): Promise<IBulkEditResult>;
}

24 changes: 0 additions & 24 deletions src/vs/editor/common/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Color } from 'vs/base/common/color';
import { Event } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isObject } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
Expand Down Expand Up @@ -1337,29 +1336,6 @@ export class FoldingRangeKind {
}
}

/**
* @internal
*/
export namespace WorkspaceFileEdit {
/**
* @internal
*/
export function is(thing: any): thing is WorkspaceFileEdit {
return isObject(thing) && (Boolean((<WorkspaceFileEdit>thing).newUri) || Boolean((<WorkspaceFileEdit>thing).oldUri));
}
}

/**
* @internal
*/
export namespace WorkspaceTextEdit {
/**
* @internal
*/
export function is(thing: any): thing is WorkspaceTextEdit {
return isObject(thing) && URI.isUri((<WorkspaceTextEdit>thing).resource) && isObject((<WorkspaceTextEdit>thing).edit);
}
}

export interface WorkspaceEditMetadata {
needsConfirmation: boolean;
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/contrib/codeAction/codeActionCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService';
import { IPosition } from 'vs/editor/common/core/position';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
Expand Down Expand Up @@ -163,7 +163,7 @@ export async function applyCodeAction(
});

if (action.edit) {
await bulkEditService.apply(action.edit, { editor, label: action.title });
await bulkEditService.apply(ResourceEdit.convert(action.edit), { editor, label: action.title });
}

if (action.command) {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/contrib/rename/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { MessageController } from 'vs/editor/contrib/message/messageController';
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/browser/core/editorState';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
Expand Down Expand Up @@ -226,7 +226,7 @@ class RenameController implements IEditorContribution {
return;
}

this._bulkEditService.apply(renameResult, {
this._bulkEditService.apply(ResourceEdit.convert(renameResult), {
editor: this.editor,
showPreview: inputFieldResult.wantsPreview,
label: nls.localize('label', "Renaming '{0}'", loc?.text),
Expand Down
52 changes: 26 additions & 26 deletions src/vs/editor/standalone/browser/simpleServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import { OS, isLinux, isMacintosh } from 'vs/base/common/platform';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor, IDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IBulkEditOptions, IBulkEditResult, IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { IBulkEditOptions, IBulkEditResult, IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { isDiffEditorConfigurationKey, isEditorConfigurationKey } from 'vs/editor/common/config/commonEditorConfig';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { IPosition, Position as Pos } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IEditor } from 'vs/editor/common/editorCommon';
import { ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { TextEdit, WorkspaceEdit, WorkspaceTextEdit } from 'vs/editor/common/modes';
import { IIdentifiedSingleEditOperation, ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService';
Expand Down Expand Up @@ -665,42 +664,43 @@ export class SimpleBulkEditService implements IBulkEditService {
return Disposable.None;
}

apply(workspaceEdit: WorkspaceEdit, options?: IBulkEditOptions): Promise<IBulkEditResult> {
async apply(edits: ResourceEdit[], _options?: IBulkEditOptions): Promise<IBulkEditResult> {

let edits = new Map<ITextModel, TextEdit[]>();
const textEdits = new Map<ITextModel, IIdentifiedSingleEditOperation[]>();

if (workspaceEdit.edits) {
for (let edit of workspaceEdit.edits) {
if (!WorkspaceTextEdit.is(edit)) {
return Promise.reject(new Error('bad edit - only text edits are supported'));
}
let model = this._modelService.getModel(edit.resource);
if (!model) {
return Promise.reject(new Error('bad edit - model not found'));
}
let array = edits.get(model);
if (!array) {
array = [];
edits.set(model, array);
}
array.push(edit.edit);
for (let edit of edits) {
if (!(edit instanceof ResourceTextEdit)) {
throw new Error('bad edit - only text edits are supported');
}
const model = this._modelService.getModel(edit.resource);
if (!model) {
throw new Error('bad edit - model not found');
}
if (typeof edit.versionId === 'number' && model.getVersionId() !== edit.versionId) {
throw new Error('bad state - model changed in the meantime');
}
let array = textEdits.get(model);
if (!array) {
array = [];
textEdits.set(model, array);
}
array.push(EditOperation.replaceMove(Range.lift(edit.textEdit.range), edit.textEdit.text));
}


let totalEdits = 0;
let totalFiles = 0;
edits.forEach((edits, model) => {
for (const [model, edits] of textEdits) {
model.pushStackElement();
model.pushEditOperations([], edits.map((e) => EditOperation.replaceMove(Range.lift(e.range), e.text)), () => []);
model.pushEditOperations([], edits, () => []);
model.pushStackElement();
totalFiles += 1;
totalEdits += edits.length;
});
}

return Promise.resolve({
selection: undefined,
return {
ariaSummary: strings.format(SimpleServicesNLS.bulkEditServiceSummary, totalEdits, totalFiles)
});
};
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/api/browser/mainThreadEditors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from 'vs/editor/browser/services/bulkEditService';
import { IBulkEditService, ResourceEdit } 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';
Expand Down Expand Up @@ -223,7 +223,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {

$tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise<boolean> {
const { edits } = reviveWorkspaceEditDto(dto)!;
return this._bulkEditService.apply({ edits }).then(() => true, _err => false);
return this._bulkEditService.apply(ResourceEdit.convert({ edits })).then(() => true, _err => false);
}

$tryInsertSnippet(id: string, template: string, ranges: readonly IRange[], opts: IUndoStopOptions): Promise<boolean> {
Expand Down
72 changes: 30 additions & 42 deletions src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import { localize } from 'vs/nls';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IBulkEditOptions, IBulkEditResult, IBulkEditService, IBulkEditPreviewHandler } from 'vs/editor/browser/services/bulkEditService';
import { WorkspaceFileEdit, WorkspaceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes';
import { IBulkEditOptions, IBulkEditResult, IBulkEditService, IBulkEditPreviewHandler, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress';
Expand All @@ -16,22 +15,19 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { BulkTextEdits } from 'vs/workbench/contrib/bulkEdit/browser/bulkTextEdits';
import { BulkFileEdits } from 'vs/workbench/contrib/bulkEdit/browser/bulkFileEdits';
import { ResourceMap } from 'vs/base/common/map';

type Edit = WorkspaceFileEdit | WorkspaceTextEdit;

class BulkEdit {

private readonly _label: string | undefined;
private readonly _edits: Edit[] = [];
private readonly _edits: ResourceEdit[] = [];
private readonly _editor: ICodeEditor | undefined;
private readonly _progress: IProgress<IProgressStep>;

constructor(
label: string | undefined,
editor: ICodeEditor | undefined,
progress: IProgress<IProgressStep> | undefined,
edits: Edit[],
edits: ResourceEdit[],
@IInstantiationService private readonly _instaService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
) {
Expand All @@ -55,52 +51,44 @@ class BulkEdit {

async perform(): Promise<void> {

let seen = new ResourceMap<true>();
let total = 0;

const groups: Edit[][] = [];
let group: Edit[] | undefined;
for (const edit of this._edits) {
if (!group
|| (WorkspaceFileEdit.is(group[0]) && !WorkspaceFileEdit.is(edit))
|| (WorkspaceTextEdit.is(group[0]) && !WorkspaceTextEdit.is(edit))
) {
group = [];
groups.push(group);
}
group.push(edit);
if (this._edits.length === 0) {
return;
}

if (WorkspaceFileEdit.is(edit)) {
total += 1;
} else if (!seen.has(edit.resource)) {
seen.set(edit.resource, true);
total += 2;
const ranges: number[] = [1];
for (let i = 1; i < this._edits.length; i++) {
if (Object.getPrototypeOf(this._edits[i - 1]) === Object.getPrototypeOf(this._edits[i])) {
ranges[ranges.length - 1]++;
} else {
ranges.push(1);
}
}

// define total work and progress callback
// for child operations
this._progress.report({ total });

this._progress.report({ total: this._edits.length });
const progress: IProgress<void> = { report: _ => this._progress.report({ increment: 1 }) };

// do it.
for (const group of groups) {
if (WorkspaceFileEdit.is(group[0])) {
await this._performFileEdits(<WorkspaceFileEdit[]>group, progress);

let index = 0;
for (let range of ranges) {
const group = this._edits.slice(index, index + range);
if (group[0] instanceof ResourceFileEdit) {
await this._performFileEdits(<ResourceFileEdit[]>group, progress);
} else if (group[0] instanceof ResourceTextEdit) {
await this._performTextEdits(<ResourceTextEdit[]>group, progress);
} else {
await this._performTextEdits(<WorkspaceTextEdit[]>group, progress);
console.log('UNKNOWN EDIT');
}
index = index + range;
}
}

private async _performFileEdits(edits: WorkspaceFileEdit[], progress: IProgress<void>) {
private async _performFileEdits(edits: ResourceFileEdit[], progress: IProgress<void>) {
this._logService.debug('_performFileEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkFileEdits, this._label || localize('workspaceEdit', "Workspace Edit"), progress, edits);
await model.apply();
}

private async _performTextEdits(edits: WorkspaceTextEdit[], progress: IProgress<void>): Promise<void> {
private async _performTextEdits(edits: ResourceTextEdit[], progress: IProgress<void>): Promise<void> {
this._logService.debug('_performTextEdits', JSON.stringify(edits));
const model = this._instaService.createInstance(BulkTextEdits, this._label || localize('workspaceEdit', "Workspace Edit"), this._editor, progress, edits);
await model.apply();
Expand Down Expand Up @@ -132,17 +120,17 @@ export class BulkEditService implements IBulkEditService {
return Boolean(this._previewHandler);
}

async apply(edit: WorkspaceEdit, options?: IBulkEditOptions): Promise<IBulkEditResult> {
async apply(edits: ResourceEdit[], options?: IBulkEditOptions): Promise<IBulkEditResult> {

if (edit.edits.length === 0) {
if (edits.length === 0) {
return { ariaSummary: localize('nothing', "Made no edits") };
}

if (this._previewHandler && (options?.showPreview || edit.edits.some(value => value.metadata?.needsConfirmation))) {
edit = await this._previewHandler(edit, options);
if (this._previewHandler && (options?.showPreview || edits.some(value => value.metadata?.needsConfirmation))) {
edits = await this._previewHandler(edits, options);
}

const { edits } = edit;
// const { edits } = edit;
let codeEditor = options?.editor;
// try to find code editor
if (!codeEditor) {
Expand Down
Loading

0 comments on commit 0681925

Please sign in to comment.