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

Adopt the UndoRedo service in the editor

上级 757006f4
......@@ -25,6 +25,7 @@ import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
const CORE_WEIGHT = KeybindingWeight.EditorCore;
......@@ -1529,6 +1530,102 @@ export namespace CoreNavigationCommands {
});
}
/**
* A command that will:
* 1. invoke a command on the focused editor.
* 2. otherwise, invoke a browser built-in command on the `activeElement`.
* 3. otherwise, invoke a command on the workbench active editor.
*/
abstract class EditorOrNativeTextInputCommand extends Command {
public runCommand(accessor: ServicesAccessor, args: any): void {
const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
// Only if editor text focus (i.e. not if editor has widget focus).
if (focusedEditor && focusedEditor.hasTextFocus()) {
return this.runEditorCommand(accessor, focusedEditor, args);
}
// Ignore this action when user is focused on an element that allows for entering text
const activeElement = <HTMLElement>document.activeElement;
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
return this.runDOMCommand();
}
// Redirecting to active editor
const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor();
if (activeEditor) {
activeEditor.focus();
return this.runEditorCommand(accessor, activeEditor, args);
}
}
public abstract runDOMCommand(): void;
public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void;
}
class SelectAllCommand extends EditorOrNativeTextInputCommand {
constructor() {
super({
id: 'editor.action.selectAll',
precondition: EditorContextKeys.textInputFocus,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: null,
primary: KeyMod.CtrlCmd | KeyCode.KEY_A
},
menuOpts: [{
menuId: MenuId.MenubarSelectionMenu,
group: '1_basic',
title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('selectAll', "Select All"),
order: 1
}]
});
}
public runDOMCommand(): void {
document.execCommand('selectAll');
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
args = args || {};
args.source = 'keyboard';
CoreNavigationCommands.SelectAll.runEditorCommand(accessor, editor, args);
}
}
class UndoCommand extends EditorOrNativeTextInputCommand {
public runDOMCommand(): void {
document.execCommand('undo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().undo();
}
}
class RedoCommand extends EditorOrNativeTextInputCommand {
public runDOMCommand(): void {
document.execCommand('redo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().redo();
}
}
function registerCommand<T extends Command>(command: T): T {
command.register();
return command;
}
export namespace CoreEditingCommands {
export abstract class CoreEditingCommand extends EditorCommand {
......@@ -1659,62 +1756,53 @@ export namespace CoreEditingCommands {
}
});
}
function registerCommand(command: Command) {
command.register();
}
/**
* A command that will:
* 1. invoke a command on the focused editor.
* 2. otherwise, invoke a browser built-in command on the `activeElement`.
* 3. otherwise, invoke a command on the workbench active editor.
*/
class EditorOrNativeTextInputCommand extends Command {
private readonly _editorHandler: string | EditorCommand;
private readonly _inputHandler: string;
constructor(opts: ICommandOptions & { editorHandler: string | EditorCommand; inputHandler: string; }) {
super(opts);
this._editorHandler = opts.editorHandler;
this._inputHandler = opts.inputHandler;
}
public runCommand(accessor: ServicesAccessor, args: any): void {
const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
// Only if editor text focus (i.e. not if editor has widget focus).
if (focusedEditor && focusedEditor.hasTextFocus()) {
return this._runEditorHandler(accessor, focusedEditor, args);
}
export const Undo: UndoCommand = registerCommand(new UndoCommand({
id: 'undo',
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('undo', "Undo"),
order: 1
}]
}));
// Ignore this action when user is focused on an element that allows for entering text
const activeElement = <HTMLElement>document.activeElement;
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
document.execCommand(this._inputHandler);
return;
}
export const DefaultUndo: UndoCommand = registerCommand(new UndoCommand({ id: 'default:undo', precondition: EditorContextKeys.writable }));
// Redirecting to active editor
const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor();
if (activeEditor) {
activeEditor.focus();
return this._runEditorHandler(accessor, activeEditor, args);
}
}
export const Redo: RedoCommand = registerCommand(new RedoCommand({
id: 'redo',
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Y,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z],
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
order: 2
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('redo', "Redo"),
order: 1
}]
}));
private _runEditorHandler(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
const HANDLER = this._editorHandler;
if (typeof HANDLER === 'string') {
editor.trigger('keyboard', HANDLER, args);
} else {
args = args || {};
args.source = 'keyboard';
HANDLER.runEditorCommand(accessor, editor, args);
}
}
export const DefaultRedo: RedoCommand = registerCommand(new RedoCommand({ id: 'default:redo', precondition: EditorContextKeys.writable }));
}
/**
......@@ -1743,78 +1831,7 @@ class EditorHandlerCommand extends Command {
}
}
registerCommand(new EditorOrNativeTextInputCommand({
editorHandler: CoreNavigationCommands.SelectAll,
inputHandler: 'selectAll',
id: 'editor.action.selectAll',
precondition: EditorContextKeys.textInputFocus,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: null,
primary: KeyMod.CtrlCmd | KeyCode.KEY_A
},
menuOpts: [{
menuId: MenuId.MenubarSelectionMenu,
group: '1_basic',
title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('selectAll', "Select All"),
order: 1
}]
}));
registerCommand(new EditorOrNativeTextInputCommand({
editorHandler: Handler.Undo,
inputHandler: 'undo',
id: Handler.Undo,
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('undo', "Undo"),
order: 1
}]
}));
registerCommand(new EditorHandlerCommand('default:' + Handler.Undo, Handler.Undo));
registerCommand(new EditorOrNativeTextInputCommand({
editorHandler: Handler.Redo,
inputHandler: 'redo',
id: Handler.Redo,
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Y,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z],
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
order: 2
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('redo', "Redo"),
order: 1
}]
}));
registerCommand(new EditorHandlerCommand('default:' + Handler.Redo, Handler.Redo));
registerCommand(new SelectAllCommand());
function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void {
registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId));
......
......@@ -1543,11 +1543,18 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
};
}
const onDidChangeTextFocus = (textFocus: boolean) => {
if (this._modelData) {
this._modelData.cursor.setHasFocus(textFocus);
}
this._editorTextFocus.setValue(textFocus);
};
const viewOutgoingEvents = new ViewOutgoingEvents(viewModel);
viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e);
viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e);
viewOutgoingEvents.onDidGainFocus = () => this._editorTextFocus.setValue(true);
viewOutgoingEvents.onDidLoseFocus = () => this._editorTextFocus.setValue(false);
viewOutgoingEvents.onDidGainFocus = () => onDidChangeTextFocus(true);
viewOutgoingEvents.onDidLoseFocus = () => onDidChangeTextFocus(false);
viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e);
viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e);
viewOutgoingEvents.onMouseUp = (e) => this._onMouseUp.fire(e);
......
......@@ -16,7 +16,7 @@ import { Range, IRange } from 'vs/editor/common/core/range';
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import { RawContentChangedType, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { dispose } from 'vs/base/common/lifecycle';
......@@ -186,6 +186,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
public context: CursorContext;
private _cursors: CursorCollection;
private _hasFocus: boolean;
private _isHandling: boolean;
private _isDoingComposition: boolean;
private _selectionsWhenCompositionStarted: Selection[] | null;
......@@ -202,6 +203,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this.context = new CursorContext(this._configuration, this._model, this._viewModel);
this._cursors = new CursorCollection(this.context);
this._hasFocus = false;
this._isHandling = false;
this._isDoingComposition = false;
this._selectionsWhenCompositionStarted = null;
......@@ -215,8 +217,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
return;
}
let hadFlushEvent = e.containsEvent(RawContentChangedType.Flush);
this._onModelContentChanged(hadFlushEvent);
this._onModelContentChanged(e);
}));
this._register(viewModel.addEventListener((events: viewEvents.ViewEvent[]) => {
......@@ -264,6 +265,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
super.dispose();
}
public setHasFocus(hasFocus: boolean): void {
this._hasFocus = hasFocus;
}
private _validateAutoClosedActions(): void {
if (this._autoClosedActions.length > 0) {
let selections: Range[] = this._cursors.getSelections();
......@@ -392,8 +397,9 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this.reveal('restoreState', true, RevealTarget.Primary, editorCommon.ScrollType.Immediate);
}
private _onModelContentChanged(hadFlushEvent: boolean): void {
private _onModelContentChanged(e: ModelRawContentChangedEvent): void {
const hadFlushEvent = e.containsEvent(RawContentChangedType.Flush);
this._prevEditOperationType = EditOperationType.Other;
if (hadFlushEvent) {
......@@ -403,8 +409,13 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._validateAutoClosedActions();
this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null);
} else {
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers));
if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) {
const cursorState = CursorState.fromModelSelections(e.resultingSelection);
this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState);
} else {
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers));
}
}
}
......@@ -704,11 +715,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
const oldState = new CursorModelState(this._model, this);
let cursorChangeReason = CursorChangeReason.NotSet;
if (handlerId !== H.Undo && handlerId !== H.Redo) {
// TODO@Alex: if the undo/redo stack contains non-null selections
// it would also be OK to stop tracking selections here
this._cursors.stopTrackingSelections();
}
this._cursors.stopTrackingSelections();
// ensure valid state on all cursors
this._cursors.ensureValidState();
......@@ -734,16 +741,6 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._cut();
break;
case H.Undo:
cursorChangeReason = CursorChangeReason.Undo;
this._interpretCommandResult(this._model.undo());
break;
case H.Redo:
cursorChangeReason = CursorChangeReason.Redo;
this._interpretCommandResult(this._model.redo());
break;
case H.ExecuteCommand:
this._externalExecuteCommand(<editorCommon.ICommand>payload);
break;
......@@ -762,9 +759,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._isHandling = false;
if (handlerId !== H.Undo && handlerId !== H.Redo) {
this._cursors.startTrackingSelections();
}
this._cursors.startTrackingSelections();
this._validateAutoClosedActions();
......
......@@ -678,9 +678,5 @@ export const Handler = {
CompositionStart: 'compositionStart',
CompositionEnd: 'compositionEnd',
Paste: 'paste',
Cut: 'cut',
Undo: 'undo',
Redo: 'redo',
};
......@@ -379,6 +379,13 @@ export interface IValidEditOperation {
forceMoveMarkers: boolean;
}
/**
* @internal
*/
export interface IValidEditOperations {
operations: IValidEditOperation[];
}
/**
* A callback that can compute the cursor state after applying a series of edit operations.
*/
......@@ -1086,18 +1093,28 @@ export interface ITextModel {
*/
applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[];
/**
* @internal
*/
_applyEdits(edits: IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[];
/**
* Change the end of line sequence without recording in the undo stack.
* This can have dire consequences on the undo stack! See @pushEOL for the preferred way.
*/
setEOL(eol: EndOfLineSequence): void;
/**
* @internal
*/
_setEOL(eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
/**
* Undo edit operations until the first previous stop point created by `pushStackElement`.
* The inverse edit operations will be pushed on the redo stack.
* @internal
*/
undo(): Selection[] | null;
undo(): void;
/**
* Is there anything in the undo stack?
......@@ -1110,7 +1127,7 @@ export interface ITextModel {
* The inverse edit operations will be pushed on the undo stack.
* @internal
*/
redo(): Selection[] | null;
redo(): void;
/**
* Is there anything in the redo stack?
......
......@@ -3,61 +3,72 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Selection } from 'vs/editor/common/core/selection';
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel, IValidEditOperations } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IUndoRedoService, IUndoRedoElement, IUndoRedoContext } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
interface IEditOperation {
operations: IValidEditOperation[];
}
class EditStackElement implements IUndoRedoElement {
interface IStackElement {
readonly beforeVersionId: number;
readonly beforeCursorState: Selection[] | null;
readonly afterCursorState: Selection[] | null;
readonly afterVersionId: number;
public readonly label: string;
private _isOpen: boolean;
private readonly _model: TextModel;
private readonly _beforeVersionId: number;
private readonly _beforeCursorState: Selection[];
private _afterVersionId: number;
private _afterCursorState: Selection[] | null;
private _edits: IValidEditOperations[];
undo(model: TextModel): void;
redo(model: TextModel): void;
}
public get resources(): readonly URI[] {
return [this._model.uri];
}
class EditStackElement implements IStackElement {
public readonly beforeVersionId: number;
public readonly beforeCursorState: Selection[];
public afterCursorState: Selection[] | null;
public afterVersionId: number;
constructor(model: TextModel, beforeVersionId: number, beforeCursorState: Selection[], afterVersionId: number, afterCursorState: Selection[] | null, operations: IValidEditOperation[]) {
this.label = nls.localize('edit', "Typing");
this._isOpen = true;
this._model = model;
this._beforeVersionId = beforeVersionId;
this._beforeCursorState = beforeCursorState;
this._afterVersionId = afterVersionId;
this._afterCursorState = afterCursorState;
this._edits = [{ operations: operations }];
}
public editOperations: IEditOperation[];
public isOpen(): boolean {
return this._isOpen;
}
constructor(beforeVersionId: number, beforeCursorState: Selection[]) {
this.beforeVersionId = beforeVersionId;
this.beforeCursorState = beforeCursorState;
this.afterCursorState = null;
this.afterVersionId = -1;
this.editOperations = [];
public append(operations: IValidEditOperation[], afterVersionId: number, afterCursorState: Selection[] | null): void {
this._edits.push({ operations: operations });
this._afterVersionId = afterVersionId;
this._afterCursorState = afterCursorState;
}
public undo(model: TextModel): void {
// Apply all operations in reverse order
for (let i = this.editOperations.length - 1; i >= 0; i--) {
this.editOperations[i] = {
operations: model.applyEdits(this.editOperations[i].operations)
};
}
public close(): void {
this._isOpen = false;
}
public redo(model: TextModel): void {
// Apply all operations
for (let i = 0; i < this.editOperations.length; i++) {
this.editOperations[i] = {
operations: model.applyEdits(this.editOperations[i].operations)
};
}
undo(ctx: IUndoRedoContext): void {
this._isOpen = false;
this._edits.reverse();
this._edits = this._model._applyEdits(this._edits, true, false, this._beforeVersionId, this._beforeCursorState);
}
redo(ctx: IUndoRedoContext): void {
this._isOpen = false;
this._edits.reverse();
this._edits = this._model._applyEdits(this._edits, false, true, this._afterVersionId, this._afterCursorState);
}
invalidate(resource: URI): void {
// nothing to do
}
}
function getModelEOL(model: TextModel): EndOfLineSequence {
function getModelEOL(model: ITextModel): EndOfLineSequence {
const eol = model.getEOL();
if (eol === '\n') {
return EndOfLineSequence.LF;
......@@ -66,32 +77,40 @@ function getModelEOL(model: TextModel): EndOfLineSequence {
}
}
class EOLStackElement implements IStackElement {
public readonly beforeVersionId: number;
public readonly beforeCursorState: Selection[] | null;
public readonly afterCursorState: Selection[] | null;
public afterVersionId: number;
class EOLStackElement implements IUndoRedoElement {
public eol: EndOfLineSequence;
public readonly label: string;
private readonly _model: TextModel;
private readonly _beforeVersionId: number;
private readonly _afterVersionId: number;
private _eol: EndOfLineSequence;
constructor(beforeVersionId: number, setEOL: EndOfLineSequence) {
this.beforeVersionId = beforeVersionId;
this.beforeCursorState = null;
this.afterCursorState = null;
this.afterVersionId = -1;
this.eol = setEOL;
public get resources(): readonly URI[] {
return [this._model.uri];
}
public undo(model: TextModel): void {
let redoEOL = getModelEOL(model);
model.setEOL(this.eol);
this.eol = redoEOL;
constructor(model: TextModel, beforeVersionId: number, afterVersionId: number, eol: EndOfLineSequence) {
this.label = nls.localize('eol', "Change End Of Line Sequence");
this._model = model;
this._beforeVersionId = beforeVersionId;
this._afterVersionId = afterVersionId;
this._eol = eol;
}
public redo(model: TextModel): void {
let undoEOL = getModelEOL(model);
model.setEOL(this.eol);
this.eol = undoEOL;
undo(ctx: IUndoRedoContext): void {
const redoEOL = getModelEOL(this._model);
this._model._setEOL(this._eol, true, false, this._beforeVersionId, null);
this._eol = redoEOL;
}
redo(ctx: IUndoRedoContext): void {
const undoEOL = getModelEOL(this._model);
this._model._setEOL(this._eol, false, true, this._afterVersionId, null);
this._eol = undoEOL;
}
invalidate(resource: URI): void {
// nothing to do
}
}
......@@ -102,76 +121,52 @@ export interface IUndoRedoResult {
export class EditStack {
private readonly model: TextModel;
private currentOpenStackElement: IStackElement | null;
private past: IStackElement[];
private future: IStackElement[];
private readonly _model: TextModel;
private readonly _undoRedoService: IUndoRedoService;
constructor(model: TextModel) {
this.model = model;
this.currentOpenStackElement = null;
this.past = [];
this.future = [];
constructor(model: TextModel, undoRedoService: IUndoRedoService) {
this._model = model;
this._undoRedoService = undoRedoService;
}
public pushStackElement(): void {
if (this.currentOpenStackElement !== null) {
this.past.push(this.currentOpenStackElement);
this.currentOpenStackElement = null;
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (lastElement && lastElement instanceof EditStackElement) {
lastElement.close();
}
}
public clear(): void {
this.currentOpenStackElement = null;
this.past = [];
this.future = [];
this._undoRedoService.removeElements(this._model.uri);
}
public pushEOL(eol: EndOfLineSequence): void {
// No support for parallel universes :(
this.future = [];
if (this.currentOpenStackElement) {
this.pushStackElement();
const beforeVersionId = this._model.getAlternativeVersionId();
const inverseEOL = getModelEOL(this._model);
this._model.setEOL(eol);
const afterVersionId = this._model.getAlternativeVersionId();
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (lastElement && lastElement instanceof EditStackElement) {
lastElement.close();
}
const prevEOL = getModelEOL(this.model);
let stackElement = new EOLStackElement(this.model.getAlternativeVersionId(), prevEOL);
this.model.setEOL(eol);
stackElement.afterVersionId = this.model.getVersionId();
this.currentOpenStackElement = stackElement;
this.pushStackElement();
this._undoRedoService.pushElement(new EOLStackElement(this._model, inverseEOL, beforeVersionId, afterVersionId));
}
public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null {
// No support for parallel universes :(
this.future = [];
let stackElement: EditStackElement | null = null;
if (this.currentOpenStackElement) {
if (this.currentOpenStackElement instanceof EditStackElement) {
stackElement = this.currentOpenStackElement;
} else {
this.pushStackElement();
}
}
if (!this.currentOpenStackElement) {
stackElement = new EditStackElement(this.model.getAlternativeVersionId(), beforeCursorState);
this.currentOpenStackElement = stackElement;
const beforeVersionId = this._model.getAlternativeVersionId();
const inverseEditOperations = this._model.applyEdits(editOperations);
const afterVersionId = this._model.getAlternativeVersionId();
const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (lastElement && lastElement instanceof EditStackElement && lastElement.isOpen()) {
lastElement.append(inverseEditOperations, afterVersionId, afterCursorState);
} else {
this._undoRedoService.pushElement(new EditStackElement(this._model, beforeVersionId, beforeCursorState, afterVersionId, afterCursorState, inverseEditOperations));
}
const inverseEditOperation: IEditOperation = {
operations: this.model.applyEdits(editOperations)
};
stackElement!.editOperations.push(inverseEditOperation);
stackElement!.afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperation.operations);
stackElement!.afterVersionId = this.model.getVersionId();
return stackElement!.afterCursorState;
return afterCursorState;
}
private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null {
......@@ -182,62 +177,4 @@ export class EditStack {
return null;
}
}
public undo(): IUndoRedoResult | null {
this.pushStackElement();
if (this.past.length > 0) {
const pastStackElement = this.past.pop()!;
try {
pastStackElement.undo(this.model);
} catch (e) {
onUnexpectedError(e);
this.clear();
return null;
}
this.future.push(pastStackElement);
return {
selections: pastStackElement.beforeCursorState,
recordedVersionId: pastStackElement.beforeVersionId
};
}
return null;
}
public canUndo(): boolean {
return (this.past.length > 0) || this.currentOpenStackElement !== null;
}
public redo(): IUndoRedoResult | null {
if (this.future.length > 0) {
const futureStackElement = this.future.pop()!;
try {
futureStackElement.redo(this.model);
} catch (e) {
onUnexpectedError(e);
this.clear();
return null;
}
this.past.push(futureStackElement);
return {
selections: futureStackElement.afterCursorState,
recordedVersionId: futureStackElement.afterVersionId
};
}
return null;
}
public canRedo(): boolean {
return (this.future.length > 0);
}
}
......@@ -255,6 +255,7 @@ export class TextModel extends Disposable implements model.ITextModel {
public readonly id: string;
public readonly isForSimpleWidget: boolean;
private readonly _associatedResource: URI;
private readonly _undoRedoService: IUndoRedoService;
private _attachedEditorCount: number;
private _buffer: model.ITextBuffer;
private _options: model.TextModelResolvedOptions;
......@@ -270,7 +271,7 @@ export class TextModel extends Disposable implements model.ITextModel {
private readonly _isTooLargeForTokenization: boolean;
//#region Editing
private _commandManager: EditStack;
private readonly _commandManager: EditStack;
private _isUndoing: boolean;
private _isRedoing: boolean;
private _trimAutoWhitespaceLines: number[] | null;
......@@ -313,6 +314,7 @@ export class TextModel extends Disposable implements model.ITextModel {
} else {
this._associatedResource = associatedResource;
}
this._undoRedoService = undoRedoService;
this._attachedEditorCount = 0;
this._buffer = createTextBuffer(source, creationOptions.defaultEOL);
......@@ -355,7 +357,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._decorations = Object.create(null);
this._decorationsTree = new DecorationsTrees();
this._commandManager = new EditStack(this);
this._commandManager = new EditStack(this, undoRedoService);
this._isUndoing = false;
this._isRedoing = false;
this._trimAutoWhitespaceLines = null;
......@@ -370,6 +372,7 @@ 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;
......@@ -444,7 +447,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._decorationsTree = new DecorationsTrees();
// Destroy my edit history and settings
this._commandManager = new EditStack(this);
this._commandManager.clear();
this._trimAutoWhitespaceLines = null;
this._emitContentChangedEvent(
......@@ -491,6 +494,21 @@ export class TextModel extends Disposable implements model.ITextModel {
);
}
_setEOL(eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
this._isUndoing = isUndoing;
this._isRedoing = isRedoing;
this.setEOL(eol);
this._overwriteAlternativeVersionId(resultingAlternativeVersionId);
} finally {
this._isUndoing = false;
this._eventEmitter.endDeferredEmit(resultingSelection);
this._onDidChangeDecorations.endDeferredEmit();
}
}
private _onBeforeEOLChange(): void {
// Ensure all decorations get their `range` set.
const versionId = this.getVersionId();
......@@ -1280,18 +1298,37 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
}
_applyEdits(edits: model.IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
this._isUndoing = isUndoing;
this._isRedoing = isRedoing;
let reverseEdits: model.IValidEditOperations[] = [];
for (let i = 0, len = edits.length; i < len; i++) {
reverseEdits[i] = { operations: this.applyEdits(edits[i].operations) };
}
this._overwriteAlternativeVersionId(resultingAlternativeVersionId);
return reverseEdits;
} finally {
this._isUndoing = false;
this._eventEmitter.endDeferredEmit(resultingSelection);
this._onDidChangeDecorations.endDeferredEmit();
}
}
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
return this._applyEdits(this._validateEditOperations(rawOperations));
return this._doApplyEdits(this._validateEditOperations(rawOperations));
} finally {
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
}
private _applyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
const oldLineCount = this._buffer.getLineCount();
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace);
......@@ -1372,62 +1409,20 @@ export class TextModel extends Disposable implements model.ITextModel {
return result.reverseEdits;
}
private _undo(): Selection[] | null {
this._isUndoing = true;
let r = this._commandManager.undo();
this._isUndoing = false;
if (!r) {
return null;
}
this._overwriteAlternativeVersionId(r.recordedVersionId);
return r.selections;
}
public undo(): Selection[] | null {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
return this._undo();
} finally {
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
public undo(): void {
this._undoRedoService.undo(this.uri);
}
public canUndo(): boolean {
return this._commandManager.canUndo();
}
private _redo(): Selection[] | null {
this._isRedoing = true;
let r = this._commandManager.redo();
this._isRedoing = false;
if (!r) {
return null;
}
this._overwriteAlternativeVersionId(r.recordedVersionId);
return r.selections;
return this._undoRedoService.canUndo(this.uri);
}
public redo(): Selection[] | null {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
return this._redo();
} finally {
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
public redo(): void {
this._undoRedoService.redo(this.uri);
}
public canRedo(): boolean {
return this._commandManager.canRedo();
return this._undoRedoService.canRedo(this.uri);
}
//#endregion
......@@ -3199,10 +3194,11 @@ export class DidChangeContentEmitter extends Disposable {
this._deferredCnt++;
}
public endDeferredEmit(): void {
public endDeferredEmit(resultingSelection: Selection[] | null = null): void {
this._deferredCnt--;
if (this._deferredCnt === 0) {
if (this._deferredEvent !== null) {
this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection;
const e = this._deferredEvent;
this._deferredEvent = null;
this._fastEmitter.fire(e);
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IRange } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
/**
* An event describing that the current mode associated with a model has changed.
......@@ -225,11 +226,14 @@ export class ModelRawContentChangedEvent {
*/
public readonly isRedoing: boolean;
public resultingSelection: Selection[] | null;
constructor(changes: ModelRawChange[], versionId: number, isUndoing: boolean, isRedoing: boolean) {
this.changes = changes;
this.versionId = versionId;
this.isUndoing = isUndoing;
this.isRedoing = isRedoing;
this.resultingSelection = null;
}
public containsEvent(type: RawContentChangedType): boolean {
......
......@@ -317,7 +317,7 @@ suite('Editor Contrib - Line Operations', () => {
assert.equal(model.getLineContent(1), 'one');
assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1));
editor.trigger('keyboard', Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Typing some text here on line one');
assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31));
});
......@@ -447,7 +447,7 @@ suite('Editor Contrib - Line Operations', () => {
assert.equal(model.getLineContent(1), 'hello my dear world');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
editor.trigger('keyboard', Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'hello my dear');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
});
......@@ -815,13 +815,13 @@ suite('Editor Contrib - Line Operations', () => {
new Selection(2, 4, 2, 4)
]);
editor.trigger('tests', Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4)
]);
editor.trigger('tests', Handler.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
......
......@@ -13,6 +13,7 @@ import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorW
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { Handler } from 'vs/editor/common/editorCommon';
import { Cursor } from 'vs/editor/common/controller/cursor';
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
suite('WordOperations', () => {
......@@ -216,7 +217,7 @@ suite('WordOperations', () => {
assert.equal(editor.getValue(), 'foo qbar baz');
cursorCommand(cursor, Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(editor.getValue(), 'foo bar baz');
});
});
......
......@@ -1240,22 +1240,22 @@ suite('Editor Controller - Regression tests', () => {
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert9');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert10');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert11');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert12');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert13');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert14');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert15');
});
......@@ -1263,12 +1263,12 @@ suite('Editor Controller - Regression tests', () => {
});
test('issue #23539: Setting model EOL isn\'t undoable', () => {
usingCursor({
text: [
'Hello',
'world'
]
}, (model, cursor) => {
withTestCodeEditor([
'Hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel()!;
assertCursor(cursor, new Position(1, 1));
model.setEOL(EndOfLineSequence.LF);
assert.equal(model.getValue(), 'Hello\nworld');
......@@ -1276,7 +1276,7 @@ suite('Editor Controller - Regression tests', () => {
model.pushEOL(EndOfLineSequence.CRLF);
assert.equal(model.getValue(), 'Hello\r\nworld');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), 'Hello\nworld');
});
});
......@@ -1301,7 +1301,7 @@ suite('Editor Controller - Regression tests', () => {
cursorCommand(cursor, H.Type, { text: '%' }, 'keyboard');
assert.equal(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2');
});
......@@ -1327,39 +1327,39 @@ suite('Editor Controller - Regression tests', () => {
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello');
assertCursor(cursor, new Position(1, 6));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), '');
assertCursor(cursor, new Position(1, 1));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello');
assertCursor(cursor, new Position(1, 6));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
});
......@@ -1735,21 +1735,21 @@ suite('Editor Controller - Regression tests', () => {
'\t just some text'
].join('\n'), '001');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [
' some lines',
' and more lines',
' just some text',
].join('\n'), '002');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [
'some lines',
'and more lines',
'just some text',
].join('\n'), '003');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [
'some lines',
'and more lines',
......@@ -1935,10 +1935,8 @@ suite('Editor Controller - Regression tests', () => {
});
test('issue #9675: Undo/Redo adds a stop in between CHN Characters', () => {
usingCursor({
text: [
]
}, (model, cursor) => {
withTestCodeEditor([], {}, (editor, cursor) => {
const model = editor.getModel()!;
assertCursor(cursor, new Position(1, 1));
// Typing sennsei in Japanese - Hiragana
......@@ -1957,7 +1955,7 @@ suite('Editor Controller - Regression tests', () => {
assert.equal(model.getLineContent(1), 'せんせい');
assertCursor(cursor, new Position(1, 5));
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), '');
assertCursor(cursor, new Position(1, 1));
});
......@@ -2138,7 +2136,7 @@ suite('Editor Controller - Regression tests', () => {
}], () => [new Selection(1, 1, 1, 1)]);
assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!');
});
......@@ -2229,12 +2227,12 @@ suite('Editor Controller - Regression tests', () => {
new Selection(1, 5, 1, 5),
]);
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assertCursor(cursor, [
new Selection(1, 4, 1, 4),
]);
cursorCommand(cursor, H.Redo, null, 'keyboard');
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assertCursor(cursor, [
new Selection(1, 5, 1, 5),
]);
......@@ -2263,7 +2261,7 @@ suite('Editor Controller - Regression tests', () => {
new Selection(1, 1, 1, 1),
]);
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assertCursor(cursor, [
new Selection(1, 1, 1, 1),
]);
......@@ -2378,49 +2376,49 @@ suite('Editor Controller - Cursor Configuration', () => {
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 1) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 2
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 2) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'M y Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 3
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 3) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 4
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 4) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 5
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My S econd Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 5
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My S econd Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 13
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 13) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Li ne123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 14
assert.equal(model.getLineContent(2), 'My Second Line123');
......@@ -2774,7 +2772,7 @@ suite('Editor Controller - Cursor Configuration', () => {
assert.equal(model.getLineContent(2), 'a ');
// Undo DeleteLeft - get us back to original indentation
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' a ');
// Nothing is broken when cursor is in (1,1)
......@@ -2859,22 +2857,22 @@ suite('Editor Controller - Cursor Configuration', () => {
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert10');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert11');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert12');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert13');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert14');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert15');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert16');
});
......@@ -2895,7 +2893,7 @@ suite('Editor Controller - Cursor Configuration', () => {
const beforeVersion = model.getVersionId();
const beforeAltVersion = model.getAlternativeVersionId();
cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
const afterVersion = model.getVersionId();
const afterAltVersion = model.getAlternativeVersionId();
......@@ -4263,7 +4261,7 @@ suite('autoClosingPairs', () => {
moveTo(cursor, lineNumber, column);
cursorCommand(cursor, H.Type, { text: chr }, 'keyboard');
assert.deepEqual(model.getLineContent(lineNumber), expected, message);
cursorCommand(cursor, H.Undo);
model.undo();
}
test('open parens: default', () => {
......@@ -5347,11 +5345,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A fir line');
assertCursor(cursor, new Selection(1, 6, 1, 6));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3));
});
......@@ -5376,11 +5374,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A firstine');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3));
});
......@@ -5410,11 +5408,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'Second line');
assertCursor(cursor, new Selection(2, 7, 2, 7));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' line');
assertCursor(cursor, new Selection(2, 1, 2, 1));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 8, 2, 8));
});
......@@ -5448,11 +5446,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), '');
assertCursor(cursor, new Selection(2, 1, 2, 1));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' line');
assertCursor(cursor, new Selection(2, 1, 2, 1));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 8, 2, 8));
});
......@@ -5479,11 +5477,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'Another text');
assertCursor(cursor, new Selection(2, 13, 2, 13));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another ');
assertCursor(cursor, new Selection(2, 9, 2, 9));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 9, 2, 9));
});
......@@ -5515,11 +5513,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'An');
assertCursor(cursor, new Selection(2, 3, 2, 3));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another ');
assertCursor(cursor, new Selection(2, 9, 2, 9));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 9, 2, 9));
});
......@@ -5539,15 +5537,15 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A first and interesting line');
assertCursor(cursor, new Selection(1, 24, 1, 24));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first and line');
assertCursor(cursor, new Selection(1, 12, 1, 12));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3));
});
......
......@@ -84,6 +84,7 @@ export function withTestCodeEditor(text: string | string[] | null, options: Test
}
let editor = <TestCodeEditor>createTestCodeEditor(options);
editor.getCursor()!.setHasFocus(true);
callback(editor, editor.getCursor()!);
editor.dispose();
......
......@@ -16,7 +16,7 @@ export interface IUndoRedoElement {
/**
* None, one or multiple resources that this undo/redo element impacts.
*/
readonly resources: URI[];
readonly resources: readonly URI[];
/**
* The label of the undo/redo element.
......@@ -43,7 +43,7 @@ export interface IUndoRedoElement {
* Invalidate the edits concerning `resource`.
* i.e. the undo/redo stack for that particular resource has been destroyed.
*/
invalidate(resource: URI): boolean;
invalidate(resource: URI): void;
}
export interface IUndoRedoService {
......
......@@ -7,11 +7,12 @@ import { IUndoRedoService, IUndoRedoElement } from 'vs/platform/undoRedo/common/
import { URI } from 'vs/base/common/uri';
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
import { onUnexpectedError } from 'vs/base/common/errors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
class StackElement {
public readonly actual: IUndoRedoElement;
public readonly label: string;
public readonly resources: URI[];
public readonly resources: readonly URI[];
public readonly strResources: string[];
constructor(actual: IUndoRedoElement) {
......@@ -179,7 +180,7 @@ export class UndoRedoService implements IUndoRedoService {
return false;
}
redo(resource: URI): void {
public redo(resource: URI): void {
const strResource = uriGetComparisonKey(resource);
if (!this._editStacks.has(strResource)) {
return;
......@@ -239,3 +240,5 @@ export class UndoRedoService implements IUndoRedoService {
}
}
}
registerSingleton(IUndoRedoService, UndoRedoService);
......@@ -52,6 +52,8 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
class TestEnvironmentService extends NativeWorkbenchEnvironmentService {
......@@ -103,6 +105,7 @@ suite('KeybindingsEditing', () => {
instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService));
instantiationService.stub(IFilesConfigurationService, instantiationService.createInstance(FilesConfigurationService));
instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService)));
instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService));
instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl));
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
......
......@@ -18,6 +18,8 @@ import { URI } from 'vs/base/common/uri';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
class MyEditorModel extends EditorModel { }
class MyTextEditorModel extends BaseTextEditorModel {
......@@ -72,6 +74,7 @@ suite('Workbench editor model', () => {
function stubModelService(instantiationService: TestInstantiationService): IModelService {
instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService)));
instantiationService.stub(IUndoRedoService, new UndoRedoService());
return instantiationService.createInstance(ModelServiceImpl);
}
});
......@@ -91,6 +91,8 @@ import { IRemotePathService } from 'vs/workbench/services/path/common/remotePath
import { Direction } from 'vs/base/browser/ui/grid/grid';
import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, emptyProgress } from 'vs/platform/progress/common/progress';
import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
export import TestTextResourcePropertiesService = CommonWorkbenchTestServices.TestTextResourcePropertiesService;
export import TestContextService = CommonWorkbenchTestServices.TestContextService;
......@@ -194,6 +196,7 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i
instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl));
instantiationService.stub(IHistoryService, new TestHistoryService());
instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService));
instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService));
instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl));
instantiationService.stub(IFileService, new TestFileService());
instantiationService.stub(IBackupFileService, new TestBackupFileService());
......
......@@ -57,6 +57,7 @@ import 'vs/workbench/browser/parts/views/views';
//#region --- workbench services
import 'vs/platform/undoRedo/common/undoRedoService';
import 'vs/workbench/services/extensions/browser/extensionUrlHandler';
import 'vs/workbench/services/bulkEdit/browser/bulkEditService';
import 'vs/workbench/services/keybinding/common/keybindingEditing';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册