Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
export const IUndoRedoService = createDecorator<IUndoRedoService>('undoRedoService');
export const enum UndoRedoElementType {
Resource,
Workspace
}
export interface IResourceUndoRedoElement {
readonly type: UndoRedoElementType.Resource;
readonly resource: URI;
readonly label: string;
undo(): Promise<void> | void;
redo(): Promise<void> | void;
}
export interface IWorkspaceUndoRedoElement {
readonly type: UndoRedoElementType.Workspace;
readonly resources: readonly URI[];
readonly label: string;
undo(): Promise<void> | void;
redo(): Promise<void> | void;
/**
* If implemented, indicates that this undo/redo element can be split into multiple per resource elements.
*/
split?(): IResourceUndoRedoElement[];
/**
* If implemented, will be invoked before calling `undo()` or `redo()`.
* This is a good place to prepare everything such that the calls to `undo()` or `redo()` are synchronous.
* If a disposable is returned, it will be invoked to clean things up.
*/
prepareUndoRedo?(): Promise<IDisposable> | IDisposable | void;
}
export type IUndoRedoElement = IResourceUndoRedoElement | IWorkspaceUndoRedoElement;
export interface IPastFutureElements {
past: IUndoRedoElement[];
future: IUndoRedoElement[];
}
export interface UriComparisonKeyComputer {
getComparisonKey(uri: URI): string;
}
export class ResourceEditStackSnapshot {
constructor(
public readonly resource: URI,
public readonly elements: number[]
) { }
}
export class UndoRedoGroup {
private static _ID = 0;
public readonly id: number;
private order: number;
constructor() {
this.id = UndoRedoGroup._ID++;
this.order = 1;
}
public nextOrder(): number {
if (this.id === 0) {
return 0;
}
return this.order++;
}
public static None = new UndoRedoGroup();
}
export interface IUndoRedoService {
readonly _serviceBrand: undefined;
/**
* Register an URI -> string hasher.
* This is useful for making multiple URIs share the same undo-redo stack.
*/
registerUriComparisonKeyComputer(scheme: string, uriComparisonKeyComputer: UriComparisonKeyComputer): IDisposable;
/**
* Get the hash used internally for a certain URI.
* This uses any registered `UriComparisonKeyComputer`.
*/
getUriComparisonKey(resource: URI): string;
/**
* Add a new element to the `undo` stack.
* This will destroy the `redo` stack.
*/
pushElement(element: IUndoRedoElement, group?: UndoRedoGroup): void;
/**
* Get the last pushed element for a resource.
* If the last pushed element has been undone, returns null.
*/
getLastElement(resource: URI): IUndoRedoElement | null;
/**
* Get all the elements associated with a resource.
* This includes the past and the future.
*/
getElements(resource: URI): IPastFutureElements;
/**
* Validate or invalidate stack elements associated with a resource.
*/
setElementsValidFlag(resource: URI, isValid: boolean, filter: (element: IUndoRedoElement) => boolean): void;
/**
* Remove elements that target `resource`.
*/
removeElements(resource: URI): void;
/**
* Create a snapshot of the current elements on the undo-redo stack for a resource.
*/
createSnapshot(resource: URI): ResourceEditStackSnapshot;
/**
* Attempt (as best as possible) to restore a certain snapshot previously created with `createSnapshot` for a resource.
*/
restoreSnapshot(snapshot: ResourceEditStackSnapshot): void;
canUndo(resource: URI): boolean;
undo(resource: URI): Promise<void> | void;
canRedo(resource: URI): boolean;
redo(resource: URI): Promise<void> | void;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,218 @@
/*---------------------------------------------------------------------------------------------
* 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 { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { UndoRedoElementType, IUndoRedoElement, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
suite('UndoRedoService', () => {
function createUndoRedoService(dialogService: IDialogService = new TestDialogService()): UndoRedoService {
const notificationService = new TestNotificationService();
return new UndoRedoService(dialogService, notificationService);
}
test('simple single resource elements', () => {
const resource = URI.file('test.txt');
const service = createUndoRedoService();
assert.equal(service.canUndo(resource), false);
assert.equal(service.canRedo(resource), false);
assert.equal(service.hasElements(resource), false);
assert.ok(service.getLastElement(resource) === null);
let undoCall1 = 0;
let redoCall1 = 0;
const element1: IUndoRedoElement = {
type: UndoRedoElementType.Resource,
resource: resource,
label: 'typing 1',
undo: () => { undoCall1++; },
redo: () => { redoCall1++; }
};
service.pushElement(element1);
assert.equal(undoCall1, 0);
assert.equal(redoCall1, 0);
assert.equal(service.canUndo(resource), true);
assert.equal(service.canRedo(resource), false);
assert.equal(service.hasElements(resource), true);
assert.ok(service.getLastElement(resource) === element1);
service.undo(resource);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 0);
assert.equal(service.canUndo(resource), false);
assert.equal(service.canRedo(resource), true);
assert.equal(service.hasElements(resource), true);
assert.ok(service.getLastElement(resource) === null);
service.redo(resource);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 1);
assert.equal(service.canUndo(resource), true);
assert.equal(service.canRedo(resource), false);
assert.equal(service.hasElements(resource), true);
assert.ok(service.getLastElement(resource) === element1);
let undoCall2 = 0;
let redoCall2 = 0;
const element2: IUndoRedoElement = {
type: UndoRedoElementType.Resource,
resource: resource,
label: 'typing 2',
undo: () => { undoCall2++; },
redo: () => { redoCall2++; }
};
service.pushElement(element2);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 1);
assert.equal(undoCall2, 0);
assert.equal(redoCall2, 0);
assert.equal(service.canUndo(resource), true);
assert.equal(service.canRedo(resource), false);
assert.equal(service.hasElements(resource), true);
assert.ok(service.getLastElement(resource) === element2);
service.undo(resource);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 1);
assert.equal(undoCall2, 1);
assert.equal(redoCall2, 0);
assert.equal(service.canUndo(resource), true);
assert.equal(service.canRedo(resource), true);
assert.equal(service.hasElements(resource), true);
assert.ok(service.getLastElement(resource) === null);
let undoCall3 = 0;
let redoCall3 = 0;
const element3: IUndoRedoElement = {
type: UndoRedoElementType.Resource,
resource: resource,
label: 'typing 2',
undo: () => { undoCall3++; },
redo: () => { redoCall3++; }
};
service.pushElement(element3);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 1);
assert.equal(undoCall2, 1);
assert.equal(redoCall2, 0);
assert.equal(undoCall3, 0);
assert.equal(redoCall3, 0);
assert.equal(service.canUndo(resource), true);
assert.equal(service.canRedo(resource), false);
assert.equal(service.hasElements(resource), true);
assert.ok(service.getLastElement(resource) === element3);
service.undo(resource);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 1);
assert.equal(undoCall2, 1);
assert.equal(redoCall2, 0);
assert.equal(undoCall3, 1);
assert.equal(redoCall3, 0);
assert.equal(service.canUndo(resource), true);
assert.equal(service.canRedo(resource), true);
assert.equal(service.hasElements(resource), true);
assert.ok(service.getLastElement(resource) === null);
});
test('multi resource elements', async () => {
const resource1 = URI.file('test1.txt');
const resource2 = URI.file('test2.txt');
const service = createUndoRedoService(new class extends mock<IDialogService>() {
async show() {
return {
choice: 0 // confirm!
};
}
});
let undoCall1 = 0, undoCall11 = 0, undoCall12 = 0;
let redoCall1 = 0, redoCall11 = 0, redoCall12 = 0;
const element1: IUndoRedoElement = {
type: UndoRedoElementType.Workspace,
resources: [resource1, resource2],
label: 'typing 1',
undo: () => { undoCall1++; },
redo: () => { redoCall1++; },
split: () => {
return [
{
type: UndoRedoElementType.Resource,
resource: resource1,
label: 'typing 1.1',
undo: () => { undoCall11++; },
redo: () => { redoCall11++; }
},
{
type: UndoRedoElementType.Resource,
resource: resource2,
label: 'typing 1.2',
undo: () => { undoCall12++; },
redo: () => { redoCall12++; }
}
];
}
};
service.pushElement(element1);
assert.equal(service.canUndo(resource1), true);
assert.equal(service.canRedo(resource1), false);
assert.equal(service.hasElements(resource1), true);
assert.ok(service.getLastElement(resource1) === element1);
assert.equal(service.canUndo(resource2), true);
assert.equal(service.canRedo(resource2), false);
assert.equal(service.hasElements(resource2), true);
assert.ok(service.getLastElement(resource2) === element1);
await service.undo(resource1);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 0);
assert.equal(service.canUndo(resource1), false);
assert.equal(service.canRedo(resource1), true);
assert.equal(service.hasElements(resource1), true);
assert.ok(service.getLastElement(resource1) === null);
assert.equal(service.canUndo(resource2), false);
assert.equal(service.canRedo(resource2), true);
assert.equal(service.hasElements(resource2), true);
assert.ok(service.getLastElement(resource2) === null);
await service.redo(resource2);
assert.equal(undoCall1, 1);
assert.equal(redoCall1, 1);
assert.equal(undoCall11, 0);
assert.equal(redoCall11, 0);
assert.equal(undoCall12, 0);
assert.equal(redoCall12, 0);
assert.equal(service.canUndo(resource1), true);
assert.equal(service.canRedo(resource1), false);
assert.equal(service.hasElements(resource1), true);
assert.ok(service.getLastElement(resource1) === element1);
assert.equal(service.canUndo(resource2), true);
assert.equal(service.canRedo(resource2), false);
assert.equal(service.hasElements(resource2), true);
assert.ok(service.getLastElement(resource2) === element1);
});
test('UndoRedoGroup.None uses id 0', () => {
assert.equal(UndoRedoGroup.None.id, 0);
assert.equal(UndoRedoGroup.None.nextOrder(), 0);
assert.equal(UndoRedoGroup.None.nextOrder(), 0);
});
});