mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 09:09:46 +01:00
Fixes #23539
This commit is contained in:
@@ -596,11 +596,6 @@ export interface ITextModel {
|
||||
*/
|
||||
getEOL(): string;
|
||||
|
||||
/**
|
||||
* Change the end of line sequence used in the text buffer.
|
||||
*/
|
||||
setEOL(eol: EndOfLineSequence): void;
|
||||
|
||||
/**
|
||||
* Get the minimum legal column for line at `lineNumber`
|
||||
*/
|
||||
@@ -1008,6 +1003,12 @@ export interface ITextModel {
|
||||
*/
|
||||
pushEditOperations(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[];
|
||||
|
||||
/**
|
||||
* Change the end of line sequence. This is the preferred way of
|
||||
* changing the eol sequence. This will land on the undo stack.
|
||||
*/
|
||||
pushEOL(eol: EndOfLineSequence): void;
|
||||
|
||||
/**
|
||||
* Edit the model without adding the edits to the undo stack.
|
||||
* This can have dire consequences on the undo stack! See @pushEditOperations for the preferred way.
|
||||
@@ -1016,6 +1017,12 @@ export interface ITextModel {
|
||||
*/
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Undo edit operations until the first previous stop point created by `pushStackElement`.
|
||||
* The inverse edit operations will be pushed on the redo stack.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ICursorStateComputer, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { ICursorStateComputer, IIdentifiedSingleEditOperation, EndOfLineSequence } from 'vs/editor/common/model';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
|
||||
@@ -14,13 +14,86 @@ interface IEditOperation {
|
||||
}
|
||||
|
||||
interface IStackElement {
|
||||
beforeVersionId: number;
|
||||
beforeCursorState: Selection[];
|
||||
readonly beforeVersionId: number;
|
||||
readonly beforeCursorState: Selection[];
|
||||
readonly afterCursorState: Selection[];
|
||||
readonly afterVersionId: number;
|
||||
|
||||
editOperations: IEditOperation[];
|
||||
undo(model: TextModel): void;
|
||||
redo(model: TextModel): void;
|
||||
}
|
||||
|
||||
afterCursorState: Selection[];
|
||||
afterVersionId: number;
|
||||
class EditStackElement implements IStackElement {
|
||||
public readonly beforeVersionId: number;
|
||||
public readonly beforeCursorState: Selection[];
|
||||
public afterCursorState: Selection[];
|
||||
public afterVersionId: number;
|
||||
|
||||
public editOperations: IEditOperation[];
|
||||
|
||||
constructor(beforeVersionId: number, beforeCursorState: Selection[]) {
|
||||
this.beforeVersionId = beforeVersionId;
|
||||
this.beforeCursorState = beforeCursorState;
|
||||
this.afterCursorState = null;
|
||||
this.afterVersionId = -1;
|
||||
this.editOperations = [];
|
||||
}
|
||||
|
||||
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 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getModelEOL(model: TextModel): EndOfLineSequence {
|
||||
const eol = model.getEOL();
|
||||
if (eol === '\n') {
|
||||
return EndOfLineSequence.LF;
|
||||
} else {
|
||||
return EndOfLineSequence.CRLF;
|
||||
}
|
||||
}
|
||||
|
||||
class EOLStackElement implements IStackElement {
|
||||
public readonly beforeVersionId: number;
|
||||
public readonly beforeCursorState: Selection[];
|
||||
public readonly afterCursorState: Selection[];
|
||||
public afterVersionId: number;
|
||||
|
||||
public eol: EndOfLineSequence;
|
||||
|
||||
constructor(beforeVersionId: number, setEOL: EndOfLineSequence) {
|
||||
this.beforeVersionId = beforeVersionId;
|
||||
this.beforeCursorState = null;
|
||||
this.afterCursorState = null;
|
||||
this.afterVersionId = -1;
|
||||
this.eol = setEOL;
|
||||
}
|
||||
|
||||
public undo(model: TextModel): void {
|
||||
let redoEOL = getModelEOL(model);
|
||||
model.setEOL(this.eol);
|
||||
this.eol = redoEOL;
|
||||
}
|
||||
|
||||
public redo(model: TextModel): void {
|
||||
let undoEOL = getModelEOL(model);
|
||||
model.setEOL(this.eol);
|
||||
this.eol = undoEOL;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IUndoRedoResult {
|
||||
@@ -55,34 +128,60 @@ export class EditStack {
|
||||
this.future = [];
|
||||
}
|
||||
|
||||
public pushEOL(eol: EndOfLineSequence): void {
|
||||
// No support for parallel universes :(
|
||||
this.future = [];
|
||||
|
||||
if (this.currentOpenStackElement) {
|
||||
this.pushStackElement();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] {
|
||||
// No support for parallel universes :(
|
||||
this.future = [];
|
||||
|
||||
let stackElement: EditStackElement = null;
|
||||
|
||||
if (this.currentOpenStackElement) {
|
||||
if (this.currentOpenStackElement instanceof EditStackElement) {
|
||||
stackElement = this.currentOpenStackElement;
|
||||
} else {
|
||||
this.pushStackElement();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.currentOpenStackElement) {
|
||||
this.currentOpenStackElement = {
|
||||
beforeVersionId: this.model.getAlternativeVersionId(),
|
||||
beforeCursorState: beforeCursorState,
|
||||
editOperations: [],
|
||||
afterCursorState: null,
|
||||
afterVersionId: -1
|
||||
};
|
||||
stackElement = new EditStackElement(this.model.getAlternativeVersionId(), beforeCursorState);
|
||||
this.currentOpenStackElement = stackElement;
|
||||
}
|
||||
|
||||
const inverseEditOperation: IEditOperation = {
|
||||
operations: this.model.applyEdits(editOperations)
|
||||
};
|
||||
|
||||
this.currentOpenStackElement.editOperations.push(inverseEditOperation);
|
||||
stackElement.editOperations.push(inverseEditOperation);
|
||||
stackElement.afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperation.operations);
|
||||
stackElement.afterVersionId = this.model.getVersionId();
|
||||
return stackElement.afterCursorState;
|
||||
}
|
||||
|
||||
private static _computeCursorState(cursorStateComputer: ICursorStateComputer, inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] {
|
||||
try {
|
||||
this.currentOpenStackElement.afterCursorState = cursorStateComputer ? cursorStateComputer(inverseEditOperation.operations) : null;
|
||||
return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null;
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
this.currentOpenStackElement.afterCursorState = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.currentOpenStackElement.afterVersionId = this.model.getVersionId();
|
||||
return this.currentOpenStackElement.afterCursorState;
|
||||
}
|
||||
|
||||
public undo(): IUndoRedoResult {
|
||||
@@ -93,13 +192,9 @@ export class EditStack {
|
||||
const pastStackElement = this.past.pop();
|
||||
|
||||
try {
|
||||
// Apply all operations in reverse order
|
||||
for (let i = pastStackElement.editOperations.length - 1; i >= 0; i--) {
|
||||
pastStackElement.editOperations[i] = {
|
||||
operations: this.model.applyEdits(pastStackElement.editOperations[i].operations)
|
||||
};
|
||||
}
|
||||
pastStackElement.undo(this.model);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
this.clear();
|
||||
return null;
|
||||
}
|
||||
@@ -118,20 +213,12 @@ export class EditStack {
|
||||
public redo(): IUndoRedoResult {
|
||||
|
||||
if (this.future.length > 0) {
|
||||
if (this.currentOpenStackElement) {
|
||||
throw new Error('How is this possible?');
|
||||
}
|
||||
|
||||
const futureStackElement = this.future.pop();
|
||||
|
||||
try {
|
||||
// Apply all operations
|
||||
for (let i = 0; i < futureStackElement.editOperations.length; i++) {
|
||||
futureStackElement.editOperations[i] = {
|
||||
operations: this.model.applyEdits(futureStackElement.editOperations[i].operations)
|
||||
};
|
||||
}
|
||||
futureStackElement.redo(this.model);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
this.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1143,6 +1143,21 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this._commandManager.pushStackElement();
|
||||
}
|
||||
|
||||
public pushEOL(eol: model.EndOfLineSequence): void {
|
||||
const currentEOL = (this.getEOL() === '\n' ? model.EndOfLineSequence.LF : model.EndOfLineSequence.CRLF);
|
||||
if (currentEOL === eol) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._onDidChangeDecorations.beginDeferredEmit();
|
||||
this._eventEmitter.beginDeferredEmit();
|
||||
this._commandManager.pushEOL(eol);
|
||||
} finally {
|
||||
this._eventEmitter.endDeferredEmit();
|
||||
this._onDidChangeDecorations.endDeferredEmit();
|
||||
}
|
||||
}
|
||||
|
||||
public pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer): Selection[] {
|
||||
try {
|
||||
this._onDidChangeDecorations.beginDeferredEmit();
|
||||
|
||||
@@ -418,7 +418,7 @@ export class ModelServiceImpl implements IModelService {
|
||||
|
||||
// Otherwise find a diff between the values and update model
|
||||
model.pushStackElement();
|
||||
model.setEOL(textBuffer.getEOL() === '\r\n' ? EndOfLineSequence.CRLF : EndOfLineSequence.LF);
|
||||
model.pushEOL(textBuffer.getEOL() === '\r\n' ? EndOfLineSequence.CRLF : EndOfLineSequence.LF);
|
||||
model.pushEditOperations(
|
||||
[],
|
||||
ModelServiceImpl._computeEdits(model, textBuffer),
|
||||
|
||||
@@ -26,7 +26,7 @@ export class FormattingEdit {
|
||||
}
|
||||
|
||||
if (typeof newEol === 'number') {
|
||||
editor.getModel().setEOL(newEol);
|
||||
editor.getModel().pushEOL(newEol);
|
||||
}
|
||||
|
||||
return singleEdits;
|
||||
@@ -40,8 +40,8 @@ export class FormattingEdit {
|
||||
}
|
||||
|
||||
static execute(editor: ICodeEditor, _edits: TextEdit[]) {
|
||||
let edits = FormattingEdit._handleEolEdits(editor, _edits);
|
||||
editor.pushUndoStop();
|
||||
let edits = FormattingEdit._handleEolEdits(editor, _edits);
|
||||
if (edits.length === 1 && FormattingEdit._isFullModelReplaceEdit(editor, edits[0])) {
|
||||
// We use replace semantics and hope that markers stay put...
|
||||
editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
|
||||
|
||||
@@ -1214,6 +1214,25 @@ suite('Editor Controller - Regression tests', () => {
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('issue #23539: Setting model EOL isn\'t undoable', () => {
|
||||
usingCursor({
|
||||
text: [
|
||||
'Hello',
|
||||
'world'
|
||||
]
|
||||
}, (model, cursor) => {
|
||||
assertCursor(cursor, new Position(1, 1));
|
||||
model.setEOL(EndOfLineSequence.LF);
|
||||
assert.equal(model.getValue(), 'Hello\nworld');
|
||||
|
||||
model.pushEOL(EndOfLineSequence.CRLF);
|
||||
assert.equal(model.getValue(), 'Hello\r\nworld');
|
||||
|
||||
cursorCommand(cursor, H.Undo);
|
||||
assert.equal(model.getValue(), 'Hello\nworld');
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #47733: Undo mangles unicode characters', () => {
|
||||
const languageId = new LanguageIdentifier('myMode', 3);
|
||||
class MyMode extends MockMode {
|
||||
|
||||
Vendored
+10
-4
@@ -1505,10 +1505,6 @@ declare namespace monaco.editor {
|
||||
* @return EOL char sequence (e.g.: '\n' or '\r\n').
|
||||
*/
|
||||
getEOL(): string;
|
||||
/**
|
||||
* Change the end of line sequence used in the text buffer.
|
||||
*/
|
||||
setEOL(eol: EndOfLineSequence): void;
|
||||
/**
|
||||
* Get the minimum legal column for line at `lineNumber`
|
||||
*/
|
||||
@@ -1744,6 +1740,11 @@ declare namespace monaco.editor {
|
||||
* @return The cursor state returned by the `cursorStateComputer`.
|
||||
*/
|
||||
pushEditOperations(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[];
|
||||
/**
|
||||
* Change the end of line sequence. This is the preferred way of
|
||||
* changing the eol sequence. This will land on the undo stack.
|
||||
*/
|
||||
pushEOL(eol: EndOfLineSequence): void;
|
||||
/**
|
||||
* Edit the model without adding the edits to the undo stack.
|
||||
* This can have dire consequences on the undo stack! See @pushEditOperations for the preferred way.
|
||||
@@ -1751,6 +1752,11 @@ declare namespace monaco.editor {
|
||||
* @return The inverse edit operations, that, when applied, will bring the model back to the previous state.
|
||||
*/
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[];
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* An event emitted when the contents of the model have changed.
|
||||
* @event
|
||||
|
||||
@@ -448,9 +448,9 @@ export class MainThreadTextEditor {
|
||||
}
|
||||
|
||||
if (opts.setEndOfLine === EndOfLine.CRLF) {
|
||||
this._model.setEOL(EndOfLineSequence.CRLF);
|
||||
this._model.pushEOL(EndOfLineSequence.CRLF);
|
||||
} else if (opts.setEndOfLine === EndOfLine.LF) {
|
||||
this._model.setEOL(EndOfLineSequence.LF);
|
||||
this._model.pushEOL(EndOfLineSequence.LF);
|
||||
}
|
||||
|
||||
let transformedEdits = edits.map((edit): IIdentifiedSingleEditOperation => {
|
||||
|
||||
@@ -1122,7 +1122,7 @@ export class ChangeEOLAction extends Action {
|
||||
const editorWidget = getEditorWidget(activeEditor);
|
||||
if (editorWidget && isWritableCodeEditor(editorWidget)) {
|
||||
const textModel = editorWidget.getModel();
|
||||
textModel.setEOL(eol.eol);
|
||||
textModel.pushEOL(eol.eol);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ class EditTask implements IDisposable {
|
||||
}
|
||||
if (this._newEol !== undefined) {
|
||||
this._model.pushStackElement();
|
||||
this._model.setEOL(this._newEol);
|
||||
this._model.pushEOL(this._newEol);
|
||||
this._model.pushStackElement();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user