mirror of
https://github.com/coder/code-server.git
synced 2026-06-13 13:37:09 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
141
lib/vscode/src/vs/platform/undoRedo/common/undoRedo.ts
Normal file
141
lib/vscode/src/vs/platform/undoRedo/common/undoRedo.ts
Normal 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;
|
||||
}
|
||||
1276
lib/vscode/src/vs/platform/undoRedo/common/undoRedoService.ts
Normal file
1276
lib/vscode/src/vs/platform/undoRedo/common/undoRedoService.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user