未验证 提交 8ad94ef2 编写于 作者: A Alex Dima

Maintain undo stack across text model disposal / creation

上级 439395ee
......@@ -12,12 +12,12 @@ import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorks
import { URI } from 'vs/base/common/uri';
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
export class EditStackElement implements IResourceUndoRedoElement {
export class SingleModelEditStackElement implements IResourceUndoRedoElement {
public readonly type = UndoRedoElementType.Resource;
public readonly label: string;
private _isOpen: boolean;
public readonly model: ITextModel;
public model: ITextModel;
private readonly _beforeVersionId: number;
private readonly _beforeEOL: EndOfLineSequence;
private readonly _beforeCursorState: Selection[] | null;
......@@ -43,6 +43,10 @@ export class EditStackElement implements IResourceUndoRedoElement {
this._edits = [];
}
public setModel(model: ITextModel): void {
this.model = model;
}
public canAppend(model: ITextModel): boolean {
return (this._isOpen && this.model === model);
}
......@@ -78,8 +82,8 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
public readonly label: string;
private _isOpen: boolean;
private readonly _editStackElementsArr: EditStackElement[];
private readonly _editStackElementsMap: Map<string, EditStackElement>;
private readonly _editStackElementsArr: SingleModelEditStackElement[];
private readonly _editStackElementsMap: Map<string, SingleModelEditStackElement>;
public get resources(): readonly URI[] {
return this._editStackElementsArr.map(editStackElement => editStackElement.model.uri);
......@@ -87,18 +91,25 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
constructor(
label: string,
editStackElements: EditStackElement[]
editStackElements: SingleModelEditStackElement[]
) {
this.label = label;
this._isOpen = true;
this._editStackElementsArr = editStackElements.slice(0);
this._editStackElementsMap = new Map<string, EditStackElement>();
this._editStackElementsMap = new Map<string, SingleModelEditStackElement>();
for (const editStackElement of this._editStackElementsArr) {
const key = uriGetComparisonKey(editStackElement.model.uri);
this._editStackElementsMap.set(key, editStackElement);
}
}
public setModel(model: ITextModel): void {
const key = uriGetComparisonKey(model.uri);
if (this._editStackElementsMap.has(key)) {
this._editStackElementsMap.get(key)!.setModel(model);
}
}
public canAppend(model: ITextModel): boolean {
if (!this._isOpen) {
return false;
......@@ -140,6 +151,8 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
}
}
export type EditStackElement = SingleModelEditStackElement | MultiModelEditStackElement;
function getModelEOL(model: ITextModel): EndOfLineSequence {
const eol = model.getEOL();
if (eol === '\n') {
......@@ -149,11 +162,11 @@ function getModelEOL(model: ITextModel): EndOfLineSequence {
}
}
function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement | MultiModelEditStackElement {
function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement {
if (!element) {
return false;
}
return ((element instanceof EditStackElement) || (element instanceof MultiModelEditStackElement));
return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement));
}
export class EditStack {
......@@ -177,12 +190,12 @@ export class EditStack {
this._undoRedoService.removeElements(this._model.uri);
}
private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement | MultiModelEditStackElement {
private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement {
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (isKnownStackElement(lastElement) && lastElement.canAppend(this._model)) {
return lastElement;
}
const newElement = new EditStackElement(this._model, beforeCursorState);
const newElement = new SingleModelEditStackElement(this._model, beforeCursorState);
this._undoRedoService.pushElement(newElement);
return newElement;
}
......
......@@ -367,7 +367,6 @@ export class TextModel extends Disposable implements model.ITextModel {
this._onWillDispose.fire();
this._languageRegistryListener.dispose();
this._tokenization.dispose();
this._undoRedoService.removeElements(this.uri);
this._isDisposed = true;
super.dispose();
this._isDisposing = false;
......@@ -706,7 +705,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._alternativeVersionId = this._versionId;
}
private _overwriteAlternativeVersionId(newAlternativeVersionId: number): void {
public _overwriteAlternativeVersionId(newAlternativeVersionId: number): void {
this._alternativeVersionId = newAlternativeVersionId;
}
......
......@@ -25,7 +25,11 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { IUndoRedoService, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo';
import { StringSHA1 } from 'vs/base/common/hash';
import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement } from 'vs/editor/common/model/editStack';
export const MAINTAIN_UNDO_REDO_STACK = true;
export interface IEditorSemanticHighlightingOptions {
enabled?: boolean;
......@@ -35,6 +39,18 @@ function MODEL_ID(resource: URI): string {
return resource.toString();
}
function computeModelSha1(model: ITextModel): string {
// compute the sha1
const shaComputer = new StringSHA1();
const snapshot = model.createSnapshot();
let text: string | null;
while ((text = snapshot.read())) {
shaComputer.update(text);
}
return shaComputer.digest();
}
class ModelData implements IDisposable {
public readonly model: ITextModel;
......@@ -98,6 +114,36 @@ interface IRawConfig {
const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? DefaultEndOfLine.LF : DefaultEndOfLine.CRLF;
interface EditStackPastFutureElements {
past: EditStackElement[];
future: EditStackElement[];
}
function isEditStackPastFutureElements(undoElements: IPastFutureElements): undoElements is EditStackPastFutureElements {
return (isEditStackElements(undoElements.past) && isEditStackElements(undoElements.future));
}
function isEditStackElements(elements: IUndoRedoElement[]): elements is EditStackElement[] {
for (const element of elements) {
if (element instanceof SingleModelEditStackElement) {
continue;
}
if (element instanceof MultiModelEditStackElement) {
continue;
}
return false;
}
return true;
}
class DisposedModelInfo {
constructor(
public readonly uri: URI,
public readonly sha1: string,
public readonly alternativeVersionId: number,
) { }
}
export class ModelServiceImpl extends Disposable implements IModelService {
public _serviceBrand: undefined;
......@@ -115,14 +161,13 @@ export class ModelServiceImpl extends Disposable implements IModelService {
private readonly _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }> = this._register(new Emitter<{ model: ITextModel; oldModeId: string; }>());
public readonly onModelModeChanged: Event<{ model: ITextModel; oldModeId: string; }> = this._onModelModeChanged.event;
private _modelCreationOptionsByLanguageAndResource: {
[languageAndResource: string]: ITextModelCreationOptions;
};
private _modelCreationOptionsByLanguageAndResource: { [languageAndResource: string]: ITextModelCreationOptions; };
/**
* All the models known in the system.
*/
private readonly _models: { [modelId: string]: ModelData; };
private readonly _disposedModels: Map<string, DisposedModelInfo>;
constructor(
@IConfigurationService configurationService: IConfigurationService,
......@@ -135,8 +180,9 @@ export class ModelServiceImpl extends Disposable implements IModelService {
this._configurationService = configurationService;
this._resourcePropertiesService = resourcePropertiesService;
this._undoRedoService = undoRedoService;
this._models = {};
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
this._models = {};
this._disposedModels = new Map<string, DisposedModelInfo>();
this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions());
this._updateModelOptions();
......@@ -288,6 +334,23 @@ export class ModelServiceImpl extends Disposable implements IModelService {
// create & save the model
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService);
if (resource && this._disposedModels.has(MODEL_ID(resource))) {
const disposedModelData = this._disposedModels.get(MODEL_ID(resource))!;
this._disposedModels.delete(MODEL_ID(resource));
const elements = this._undoRedoService.getElements(resource);
if (computeModelSha1(model) === disposedModelData.sha1 && isEditStackPastFutureElements(elements)) {
for (const element of elements.past) {
element.setModel(model);
}
for (const element of elements.future) {
element.setModel(model);
}
this._undoRedoService.setElementsIsValid(resource, true);
model._overwriteAlternativeVersionId(disposedModelData.alternativeVersionId);
} else {
this._undoRedoService.removeElements(resource);
}
}
const modelId = MODEL_ID(model.uri);
if (this._models[modelId]) {
......@@ -408,6 +471,21 @@ export class ModelServiceImpl extends Disposable implements IModelService {
if (!modelData) {
return;
}
const model = modelData.model;
let maintainUndoRedoStack = false;
if (MAINTAIN_UNDO_REDO_STACK) {
const elements = this._undoRedoService.getElements(resource);
maintainUndoRedoStack = ((elements.past.length > 0 || elements.future.length > 0) && isEditStackPastFutureElements(elements));
}
if (maintainUndoRedoStack) {
// We only invalidate the elements, but they remain in the undo-redo service.
this._undoRedoService.setElementsIsValid(resource, false);
this._disposedModels.set(MODEL_ID(resource), new DisposedModelInfo(resource, computeModelSha1(model), model.getAlternativeVersionId()));
} else {
this._undoRedoService.removeElements(resource);
}
modelData.model.dispose();
}
......
......@@ -9,10 +9,11 @@ import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { createStringBuilder } from 'vs/editor/common/core/stringBuilder';
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { createTextBuffer } from 'vs/editor/common/model/textModel';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { ModelServiceImpl, MAINTAIN_UNDO_REDO_STACK } from 'vs/editor/common/services/modelServiceImpl';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
......@@ -307,6 +308,26 @@ suite('ModelService', () => {
];
assertComputeEdits(file1, file2);
});
if (MAINTAIN_UNDO_REDO_STACK) {
test('maintains undo for same resource and same content', () => {
const resource = URI.parse('file://test.txt');
// create a model
const model1 = modelService.createModel('text', null, resource);
// make an edit
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
assert.equal(model1.getValue(), 'text1');
// dispose it
modelService.destroyModel(resource);
// create a new model with the same content
const model2 = modelService.createModel('text1', null, resource);
// undo
model2.undo();
assert.equal(model2.getValue(), 'text');
});
}
});
function assertComputeEdits(lines1: string[], lines2: string[]): void {
......
......@@ -30,6 +30,13 @@ export interface IWorkspaceUndoRedoElement {
split(): IResourceUndoRedoElement[];
}
export type IUndoRedoElement = IResourceUndoRedoElement | IWorkspaceUndoRedoElement;
export interface IPastFutureElements {
past: IUndoRedoElement[];
future: IUndoRedoElement[];
}
export interface IUndoRedoService {
_serviceBrand: undefined;
......@@ -37,12 +44,18 @@ export interface IUndoRedoService {
* Add a new element to the `undo` stack.
* This will destroy the `redo` stack.
*/
pushElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void;
pushElement(element: IUndoRedoElement): void;
/**
* Get the last pushed element. If the last pushed element has been undone, returns null.
*/
getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null;
getLastElement(resource: URI): IUndoRedoElement | null;
getElements(resource: URI): IPastFutureElements;
hasElements(resource: URI): boolean;
setElementsIsValid(resource: URI, isValid: boolean): void;
/**
* Remove elements that target `resource`.
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
import { onUnexpectedError } from 'vs/base/common/errors';
......@@ -23,6 +23,7 @@ class ResourceStackElement {
public readonly strResource: string;
public readonly resources: URI[];
public readonly strResources: string[];
public isValid: boolean;
constructor(actual: IResourceUndoRedoElement) {
this.actual = actual;
......@@ -31,6 +32,11 @@ class ResourceStackElement {
this.strResource = uriGetComparisonKey(this.resource);
this.resources = [this.resource];
this.strResources = [this.strResource];
this.isValid = true;
}
public setValid(isValid: boolean): void {
this.isValid = isValid;
}
}
......@@ -39,22 +45,57 @@ const enum RemovedResourceReason {
NoParallelUniverses = 1
}
class ResourceReasonPair {
constructor(
public readonly resource: URI,
public readonly reason: RemovedResourceReason
) { }
}
class RemovedResources {
public readonly set: Set<string> = new Set<string>();
public readonly reason: [URI[], URI[]] = [[], []];
private readonly elements = new Map<string, ResourceReasonPair>();
private _getPath(resource: URI): string {
return resource.scheme === Schemas.file ? resource.fsPath : resource.path;
}
public createMessage(): string {
const externalRemoval: string[] = [];
const noParallelUniverses: string[] = [];
for (const [, element] of this.elements) {
const dest = (
element.reason === RemovedResourceReason.ExternalRemoval
? externalRemoval
: noParallelUniverses
);
dest.push(this._getPath(element.resource));
}
let messages: string[] = [];
if (this.reason[RemovedResourceReason.ExternalRemoval].length > 0) {
const paths = this.reason[RemovedResourceReason.ExternalRemoval].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path);
messages.push(nls.localize('externalRemoval', "The following files have been closed: {0}.", paths.join(', ')));
if (externalRemoval.length > 0) {
messages.push(nls.localize('externalRemoval', "The following files have been closed: {0}.", externalRemoval.join(', ')));
}
if (this.reason[RemovedResourceReason.NoParallelUniverses].length > 0) {
const paths = this.reason[RemovedResourceReason.NoParallelUniverses].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path);
messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", paths.join(', ')));
if (noParallelUniverses.length > 0) {
messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", noParallelUniverses.join(', ')));
}
return messages.join('\n');
}
public get size(): number {
return this.elements.size;
}
public has(strResource: string): boolean {
return this.elements.has(strResource);
}
public set(strResource: string, value: ResourceReasonPair): void {
this.elements.set(strResource, value);
}
public delete(strResource: string): boolean {
return this.elements.delete(strResource);
}
}
class WorkspaceStackElement {
......@@ -65,6 +106,7 @@ class WorkspaceStackElement {
public readonly resources: URI[];
public readonly strResources: string[];
public removedResources: RemovedResources | null;
public invalidatedResources: RemovedResources | null;
constructor(actual: IWorkspaceUndoRedoElement) {
this.actual = actual;
......@@ -72,18 +114,37 @@ class WorkspaceStackElement {
this.resources = actual.resources.slice(0);
this.strResources = this.resources.map(resource => uriGetComparisonKey(resource));
this.removedResources = null;
this.invalidatedResources = null;
}
public removeResource(resource: URI, strResource: string, reason: RemovedResourceReason): void {
if (!this.removedResources) {
this.removedResources = new RemovedResources();
}
if (!this.removedResources.set.has(strResource)) {
this.removedResources.set.add(strResource);
this.removedResources.reason[reason].push(resource);
if (!this.removedResources.has(strResource)) {
this.removedResources.set(strResource, new ResourceReasonPair(resource, reason));
}
}
public setValid(resource: URI, strResource: string, isValid: boolean): void {
if (isValid) {
if (this.invalidatedResources) {
this.invalidatedResources.delete(strResource);
if (this.invalidatedResources.size === 0) {
this.invalidatedResources = null;
}
}
} else {
if (!this.invalidatedResources) {
this.invalidatedResources = new RemovedResources();
}
if (!this.invalidatedResources.has(strResource)) {
this.invalidatedResources.set(strResource, new ResourceReasonPair(resource, RemovedResourceReason.ExternalRemoval));
}
}
}
}
type StackElement = ResourceStackElement | WorkspaceStackElement;
class ResourceEditStack {
......@@ -110,7 +171,7 @@ export class UndoRedoService implements IUndoRedoService {
this._editStacks = new Map<string, ResourceEditStack>();
}
public pushElement(_element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void {
public pushElement(_element: IUndoRedoElement): void {
const element: StackElement = (_element.type === UndoRedoElementType.Resource ? new ResourceStackElement(_element) : new WorkspaceStackElement(_element));
for (let i = 0, len = element.resources.length; i < len; i++) {
const resource = element.resources[i];
......@@ -131,11 +192,18 @@ export class UndoRedoService implements IUndoRedoService {
}
}
editStack.future = [];
if (editStack.past.length > 0) {
const lastElement = editStack.past[editStack.past.length - 1];
if (lastElement.type === UndoRedoElementType.Resource && !lastElement.isValid) {
// clear undo stack
editStack.past = [];
}
}
editStack.past.push(element);
}
}
public getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null {
public getLastElement(resource: URI): IUndoRedoElement | null {
const strResource = uriGetComparisonKey(resource);
if (this._editStacks.has(strResource)) {
const editStack = this._editStacks.get(strResource)!;
......@@ -150,7 +218,7 @@ export class UndoRedoService implements IUndoRedoService {
return null;
}
private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set<string> | null): void {
private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void {
const individualArr = toRemove.actual.split();
const individualMap = new Map<string, ResourceStackElement>();
for (const _element of individualArr) {
......@@ -178,7 +246,7 @@ export class UndoRedoService implements IUndoRedoService {
}
}
private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set<string> | null): void {
private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void {
const individualArr = toRemove.actual.split();
const individualMap = new Map<string, ResourceStackElement>();
for (const _element of individualArr) {
......@@ -224,6 +292,56 @@ export class UndoRedoService implements IUndoRedoService {
}
}
public setElementsIsValid(resource: URI, isValid: boolean): void {
const strResource = uriGetComparisonKey(resource);
if (this._editStacks.has(strResource)) {
const editStack = this._editStacks.get(strResource)!;
for (const element of editStack.past) {
if (element.type === UndoRedoElementType.Workspace) {
element.setValid(resource, strResource, isValid);
} else {
element.setValid(isValid);
}
}
for (const element of editStack.future) {
if (element.type === UndoRedoElementType.Workspace) {
element.setValid(resource, strResource, isValid);
} else {
element.setValid(isValid);
}
}
}
}
// resource
public hasElements(resource: URI): boolean {
const strResource = uriGetComparisonKey(resource);
if (this._editStacks.has(strResource)) {
const editStack = this._editStacks.get(strResource)!;
return (editStack.past.length > 0 || editStack.future.length > 0);
}
return false;
}
public getElements(resource: URI): IPastFutureElements {
const past: IUndoRedoElement[] = [];
const future: IUndoRedoElement[] = [];
const strResource = uriGetComparisonKey(resource);
if (this._editStacks.has(strResource)) {
const editStack = this._editStacks.get(strResource)!;
for (const element of editStack.past) {
past.push(element.actual);
}
for (const element of editStack.future) {
future.push(element.actual);
}
}
return { past, future };
}
public canUndo(resource: URI): boolean {
const strResource = uriGetComparisonKey(resource);
if (this._editStacks.has(strResource)) {
......@@ -257,11 +375,17 @@ export class UndoRedoService implements IUndoRedoService {
private _workspaceUndo(resource: URI, element: WorkspaceStackElement): Promise<void> | void {
if (element.removedResources) {
this._splitPastWorkspaceElement(element, element.removedResources.set);
this._splitPastWorkspaceElement(element, element.removedResources);
const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage());
this._notificationService.info(message);
return this.undo(resource);
}
if (element.invalidatedResources) {
this._splitPastWorkspaceElement(element, element.invalidatedResources);
const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage());
this._notificationService.info(message);
return this.undo(resource);
}
// this must be the last past element in all the impacted resources!
let affectedEditStacks: ResourceEditStack[] = [];
......@@ -313,6 +437,12 @@ export class UndoRedoService implements IUndoRedoService {
}
private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
if (!element.isValid) {
// invalid element => immediately flush edit stack!
editStack.past = [];
editStack.future = [];
return;
}
editStack.past.pop();
editStack.future.push(element);
return this._safeInvoke(element, () => element.actual.undo());
......@@ -348,11 +478,17 @@ export class UndoRedoService implements IUndoRedoService {
private _workspaceRedo(resource: URI, element: WorkspaceStackElement): Promise<void> | void {
if (element.removedResources) {
this._splitFutureWorkspaceElement(element, element.removedResources.set);
this._splitFutureWorkspaceElement(element, element.removedResources);
const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage());
this._notificationService.info(message);
return this.redo(resource);
}
if (element.invalidatedResources) {
this._splitFutureWorkspaceElement(element, element.invalidatedResources);
const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage());
this._notificationService.info(message);
return this.redo(resource);
}
// this must be the last future element in all the impacted resources!
let affectedEditStacks: ResourceEditStack[] = [];
......@@ -383,6 +519,12 @@ export class UndoRedoService implements IUndoRedoService {
}
private _resourceRedo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
if (!element.isValid) {
// invalid element => immediately flush edit stack!
editStack.past = [];
editStack.future = [];
return;
}
editStack.future.pop();
editStack.past.push(element);
return this._safeInvoke(element, () => element.actual.redo());
......
......@@ -28,7 +28,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { EditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack';
import { SingleModelEditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack';
type ValidationResult = { canApply: true } | { canApply: false, reason: URI };
......@@ -234,7 +234,7 @@ class BulkEditModel implements IDisposable {
const multiModelEditStackElement = new MultiModelEditStackElement(
this._label || localize('workspaceEdit', "Workspace Edit"),
tasks.map(t => new EditStackElement(t.model, t.getBeforeCursorState()))
tasks.map(t => new SingleModelEditStackElement(t.model, t.getBeforeCursorState()))
);
this._undoRedoService.pushElement(multiModelEditStackElement);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册