mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 073a24de05773f2261f89172987002dc0ae2f1cd (#9711)
This commit is contained in:
@@ -4,8 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as buffer from 'vs/base/common/buffer';
|
||||
|
||||
declare const TextDecoder: any; // TODO@TypeScript
|
||||
declare const TextDecoder: {
|
||||
prototype: TextDecoder;
|
||||
new(label?: string): TextDecoder;
|
||||
};
|
||||
interface TextDecoder {
|
||||
decode(view: Uint16Array): string;
|
||||
}
|
||||
@@ -18,17 +23,42 @@ export interface IStringBuilder {
|
||||
appendASCIIString(str: string): void;
|
||||
}
|
||||
|
||||
let _platformTextDecoder: TextDecoder | null;
|
||||
function getPlatformTextDecoder(): TextDecoder {
|
||||
if (!_platformTextDecoder) {
|
||||
_platformTextDecoder = new TextDecoder(platform.isLittleEndian() ? 'UTF-16LE' : 'UTF-16BE');
|
||||
}
|
||||
return _platformTextDecoder;
|
||||
}
|
||||
|
||||
export let createStringBuilder: (capacity: number) => IStringBuilder;
|
||||
export let decodeUTF16LE: (source: Uint8Array, offset: number, len: number) => string;
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
createStringBuilder = (capacity) => new StringBuilder(capacity);
|
||||
decodeUTF16LE = standardDecodeUTF16LE;
|
||||
} else {
|
||||
createStringBuilder = (capacity) => new CompatStringBuilder();
|
||||
decodeUTF16LE = compatDecodeUTF16LE;
|
||||
}
|
||||
|
||||
function standardDecodeUTF16LE(source: Uint8Array, offset: number, len: number): string {
|
||||
const view = new Uint16Array(source.buffer, offset, len);
|
||||
return getPlatformTextDecoder().decode(view);
|
||||
}
|
||||
|
||||
function compatDecodeUTF16LE(source: Uint8Array, offset: number, len: number): string {
|
||||
let result: string[] = [];
|
||||
let resultLen = 0;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const charCode = buffer.readUInt16LE(source, offset); offset += 2;
|
||||
result[resultLen++] = String.fromCharCode(charCode);
|
||||
}
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
class StringBuilder implements IStringBuilder {
|
||||
|
||||
private readonly _decoder: TextDecoder;
|
||||
private readonly _capacity: number;
|
||||
private readonly _buffer: Uint16Array;
|
||||
|
||||
@@ -36,7 +66,6 @@ class StringBuilder implements IStringBuilder {
|
||||
private _bufferLength: number;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this._decoder = new TextDecoder('UTF-16LE');
|
||||
this._capacity = capacity | 0;
|
||||
this._buffer = new Uint16Array(this._capacity);
|
||||
|
||||
@@ -63,7 +92,7 @@ class StringBuilder implements IStringBuilder {
|
||||
}
|
||||
|
||||
const view = new Uint16Array(this._buffer.buffer, 0, this._bufferLength);
|
||||
return this._decoder.decode(view);
|
||||
return getPlatformTextDecoder().decode(view);
|
||||
}
|
||||
|
||||
private _flushBuffer(): void {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
|
||||
/**
|
||||
* Vertical Lane in the overview ruler of the editor.
|
||||
@@ -373,21 +374,13 @@ export interface IValidEditOperation {
|
||||
*/
|
||||
range: Range;
|
||||
/**
|
||||
* The text to replace with. This can be null to emulate a simple delete.
|
||||
* The text to replace with. This can be empty to emulate a simple delete.
|
||||
*/
|
||||
text: string | null;
|
||||
text: string;
|
||||
/**
|
||||
* This indicates that this operation has "insert" semantics.
|
||||
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
|
||||
* @internal
|
||||
*/
|
||||
forceMoveMarkers: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IValidEditOperations {
|
||||
operations: IValidEditOperation[];
|
||||
textChange: TextChange;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1099,9 +1092,11 @@ export interface ITextModel {
|
||||
* 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.
|
||||
* @param operations The edit operations.
|
||||
* @return The inverse edit operations, that, when applied, will bring the model back to the previous state.
|
||||
* @return If desired, the inverse edit operations, that, when applied, will bring the model back to the previous state.
|
||||
*/
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[];
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[]): void;
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: false): void;
|
||||
applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: true): IValidEditOperation[];
|
||||
|
||||
/**
|
||||
* Change the end of line sequence without recording in the undo stack.
|
||||
@@ -1112,7 +1107,12 @@ export interface ITextModel {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_applyUndoRedoEdits(edits: IValidEditOperations[], eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[];
|
||||
_applyUndo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_applyRedo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
|
||||
|
||||
/**
|
||||
* Undo edit operations until the first previous stop point created by `pushStackElement`.
|
||||
@@ -1291,7 +1291,7 @@ export interface ITextBuffer {
|
||||
getLineLastNonWhitespaceColumn(lineNumber: number): number;
|
||||
|
||||
setEOL(newEOL: '\r\n' | '\n'): void;
|
||||
applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult;
|
||||
applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult;
|
||||
findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[];
|
||||
}
|
||||
|
||||
@@ -1301,7 +1301,7 @@ export interface ITextBuffer {
|
||||
export class ApplyEditsResult {
|
||||
|
||||
constructor(
|
||||
public readonly reverseEdits: IValidEditOperation[],
|
||||
public readonly reverseEdits: IValidEditOperation[] | null,
|
||||
public readonly changes: IInternalModelContentChange[],
|
||||
public readonly trimAutoWhitespaceLineNumbers: number[] | null
|
||||
) { }
|
||||
|
||||
@@ -6,69 +6,209 @@
|
||||
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, ITextModel, IValidEditOperations } from 'vs/editor/common/model';
|
||||
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
|
||||
import { TextChange, compressConsecutiveTextChanges } from 'vs/editor/common/model/textChange';
|
||||
import * as buffer from 'vs/base/common/buffer';
|
||||
|
||||
export class EditStackElement implements IResourceUndoRedoElement {
|
||||
class SingleModelEditStackData {
|
||||
|
||||
public readonly type = UndoRedoElementType.Resource;
|
||||
public readonly label: string;
|
||||
private _isOpen: boolean;
|
||||
public readonly model: ITextModel;
|
||||
private readonly _beforeVersionId: number;
|
||||
private readonly _beforeEOL: EndOfLineSequence;
|
||||
private readonly _beforeCursorState: Selection[] | null;
|
||||
private _afterVersionId: number;
|
||||
private _afterEOL: EndOfLineSequence;
|
||||
private _afterCursorState: Selection[] | null;
|
||||
private _edits: IValidEditOperations[];
|
||||
|
||||
public get resource(): URI {
|
||||
return this.model.uri;
|
||||
public static create(model: ITextModel, beforeCursorState: Selection[] | null): SingleModelEditStackData {
|
||||
const alternativeVersionId = model.getAlternativeVersionId();
|
||||
const eol = getModelEOL(model);
|
||||
return new SingleModelEditStackData(
|
||||
alternativeVersionId,
|
||||
alternativeVersionId,
|
||||
eol,
|
||||
eol,
|
||||
beforeCursorState,
|
||||
beforeCursorState,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
constructor(model: ITextModel, beforeCursorState: Selection[] | null) {
|
||||
this.label = nls.localize('edit', "Typing");
|
||||
this._isOpen = true;
|
||||
this.model = model;
|
||||
this._beforeVersionId = this.model.getAlternativeVersionId();
|
||||
this._beforeEOL = getModelEOL(this.model);
|
||||
this._beforeCursorState = beforeCursorState;
|
||||
this._afterVersionId = this._beforeVersionId;
|
||||
this._afterEOL = this._beforeEOL;
|
||||
this._afterCursorState = this._beforeCursorState;
|
||||
this._edits = [];
|
||||
}
|
||||
|
||||
public canAppend(model: ITextModel): boolean {
|
||||
return (this._isOpen && this.model === model);
|
||||
}
|
||||
constructor(
|
||||
public readonly beforeVersionId: number,
|
||||
public afterVersionId: number,
|
||||
public readonly beforeEOL: EndOfLineSequence,
|
||||
public afterEOL: EndOfLineSequence,
|
||||
public readonly beforeCursorState: Selection[] | null,
|
||||
public afterCursorState: Selection[] | null,
|
||||
public changes: TextChange[]
|
||||
) { }
|
||||
|
||||
public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
|
||||
if (operations.length > 0) {
|
||||
this._edits.push({ operations: operations });
|
||||
this.changes = compressConsecutiveTextChanges(this.changes, operations.map(op => op.textChange));
|
||||
}
|
||||
this.afterEOL = afterEOL;
|
||||
this.afterVersionId = afterVersionId;
|
||||
this.afterCursorState = afterCursorState;
|
||||
}
|
||||
|
||||
private static _writeSelectionsSize(selections: Selection[] | null): number {
|
||||
return 4 + 4 * 4 * (selections ? selections.length : 0);
|
||||
}
|
||||
|
||||
private static _writeSelections(b: Uint8Array, selections: Selection[] | null, offset: number): number {
|
||||
buffer.writeUInt32BE(b, (selections ? selections.length : 0), offset); offset += 4;
|
||||
if (selections) {
|
||||
for (const selection of selections) {
|
||||
buffer.writeUInt32BE(b, selection.selectionStartLineNumber, offset); offset += 4;
|
||||
buffer.writeUInt32BE(b, selection.selectionStartColumn, offset); offset += 4;
|
||||
buffer.writeUInt32BE(b, selection.positionLineNumber, offset); offset += 4;
|
||||
buffer.writeUInt32BE(b, selection.positionColumn, offset); offset += 4;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static _readSelections(b: Uint8Array, offset: number, dest: Selection[]): number {
|
||||
const count = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const selectionStartLineNumber = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const selectionStartColumn = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const positionLineNumber = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const positionColumn = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
dest.push(new Selection(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn));
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public serialize(): ArrayBuffer {
|
||||
let necessarySize = (
|
||||
+ 4 // beforeVersionId
|
||||
+ 4 // afterVersionId
|
||||
+ 1 // beforeEOL
|
||||
+ 1 // afterEOL
|
||||
+ SingleModelEditStackData._writeSelectionsSize(this.beforeCursorState)
|
||||
+ SingleModelEditStackData._writeSelectionsSize(this.afterCursorState)
|
||||
+ 4 // change count
|
||||
);
|
||||
for (const change of this.changes) {
|
||||
necessarySize += change.writeSize();
|
||||
}
|
||||
|
||||
const b = new Uint8Array(necessarySize);
|
||||
let offset = 0;
|
||||
buffer.writeUInt32BE(b, this.beforeVersionId, offset); offset += 4;
|
||||
buffer.writeUInt32BE(b, this.afterVersionId, offset); offset += 4;
|
||||
buffer.writeUInt8(b, this.beforeEOL, offset); offset += 1;
|
||||
buffer.writeUInt8(b, this.afterEOL, offset); offset += 1;
|
||||
offset = SingleModelEditStackData._writeSelections(b, this.beforeCursorState, offset);
|
||||
offset = SingleModelEditStackData._writeSelections(b, this.afterCursorState, offset);
|
||||
buffer.writeUInt32BE(b, this.changes.length, offset); offset += 4;
|
||||
for (const change of this.changes) {
|
||||
offset = change.write(b, offset);
|
||||
}
|
||||
return b.buffer;
|
||||
}
|
||||
|
||||
public static deserialize(source: ArrayBuffer): SingleModelEditStackData {
|
||||
const b = new Uint8Array(source);
|
||||
let offset = 0;
|
||||
const beforeVersionId = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const afterVersionId = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const beforeEOL = buffer.readUInt8(b, offset); offset += 1;
|
||||
const afterEOL = buffer.readUInt8(b, offset); offset += 1;
|
||||
const beforeCursorState: Selection[] = [];
|
||||
offset = SingleModelEditStackData._readSelections(b, offset, beforeCursorState);
|
||||
const afterCursorState: Selection[] = [];
|
||||
offset = SingleModelEditStackData._readSelections(b, offset, afterCursorState);
|
||||
const changeCount = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const changes: TextChange[] = [];
|
||||
for (let i = 0; i < changeCount; i++) {
|
||||
offset = TextChange.read(b, offset, changes);
|
||||
}
|
||||
return new SingleModelEditStackData(
|
||||
beforeVersionId,
|
||||
afterVersionId,
|
||||
beforeEOL,
|
||||
afterEOL,
|
||||
beforeCursorState,
|
||||
afterCursorState,
|
||||
changes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class SingleModelEditStackElement implements IResourceUndoRedoElement {
|
||||
|
||||
public model: ITextModel | URI;
|
||||
private _data: SingleModelEditStackData | ArrayBuffer;
|
||||
|
||||
public get type(): UndoRedoElementType.Resource {
|
||||
return UndoRedoElementType.Resource;
|
||||
}
|
||||
|
||||
public get resource(): URI {
|
||||
if (URI.isUri(this.model)) {
|
||||
return this.model;
|
||||
}
|
||||
return this.model.uri;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return nls.localize('edit', "Typing");
|
||||
}
|
||||
|
||||
constructor(model: ITextModel, beforeCursorState: Selection[] | null) {
|
||||
this.model = model;
|
||||
this._data = SingleModelEditStackData.create(model, beforeCursorState);
|
||||
}
|
||||
|
||||
public setModel(model: ITextModel | URI): void {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public canAppend(model: ITextModel): boolean {
|
||||
return (this.model === model && this._data instanceof SingleModelEditStackData);
|
||||
}
|
||||
|
||||
public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
|
||||
if (this._data instanceof SingleModelEditStackData) {
|
||||
this._data.append(model, operations, afterEOL, afterVersionId, afterCursorState);
|
||||
}
|
||||
this._afterEOL = afterEOL;
|
||||
this._afterVersionId = afterVersionId;
|
||||
this._afterCursorState = afterCursorState;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._isOpen = false;
|
||||
if (this._data instanceof SingleModelEditStackData) {
|
||||
this._data = this._data.serialize();
|
||||
}
|
||||
}
|
||||
|
||||
public undo(): void {
|
||||
this._isOpen = false;
|
||||
this._edits.reverse();
|
||||
this._edits = this.model._applyUndoRedoEdits(this._edits, this._beforeEOL, true, false, this._beforeVersionId, this._beforeCursorState);
|
||||
if (URI.isUri(this.model)) {
|
||||
// don't have a model
|
||||
throw new Error(`Invalid SingleModelEditStackElement`);
|
||||
}
|
||||
if (this._data instanceof SingleModelEditStackData) {
|
||||
this._data = this._data.serialize();
|
||||
}
|
||||
const data = SingleModelEditStackData.deserialize(this._data);
|
||||
this.model._applyUndo(data.changes, data.beforeEOL, data.beforeVersionId, data.beforeCursorState);
|
||||
}
|
||||
|
||||
public redo(): void {
|
||||
this._edits.reverse();
|
||||
this._edits = this.model._applyUndoRedoEdits(this._edits, this._afterEOL, false, true, this._afterVersionId, this._afterCursorState);
|
||||
if (URI.isUri(this.model)) {
|
||||
// don't have a model
|
||||
throw new Error(`Invalid SingleModelEditStackElement`);
|
||||
}
|
||||
if (this._data instanceof SingleModelEditStackData) {
|
||||
this._data = this._data.serialize();
|
||||
}
|
||||
const data = SingleModelEditStackData.deserialize(this._data);
|
||||
this.model._applyRedo(data.changes, data.afterEOL, data.afterVersionId, data.afterCursorState);
|
||||
}
|
||||
|
||||
public heapSize(): number {
|
||||
if (this._data instanceof SingleModelEditStackData) {
|
||||
this._data = this._data.serialize();
|
||||
}
|
||||
return this._data.byteLength + 168/*heap overhead*/;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,27 +218,34 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
|
||||
public readonly label: string;
|
||||
private _isOpen: boolean;
|
||||
|
||||
private readonly _editStackElementsArr: EditStackElement[];
|
||||
private readonly _editStackElementsMap: Map<string, EditStackElement>;
|
||||
private readonly _editStackElementsArr: SingleModelEditStackElement[];
|
||||
private readonly _editStackElementsMap: Map<string, SingleModelEditStackElement>;
|
||||
|
||||
public get resources(): readonly URI[] {
|
||||
return this._editStackElementsArr.map(editStackElement => editStackElement.model.uri);
|
||||
return this._editStackElementsArr.map(editStackElement => editStackElement.resource);
|
||||
}
|
||||
|
||||
constructor(
|
||||
label: string,
|
||||
editStackElements: EditStackElement[]
|
||||
editStackElements: SingleModelEditStackElement[]
|
||||
) {
|
||||
this.label = label;
|
||||
this._isOpen = true;
|
||||
this._editStackElementsArr = editStackElements.slice(0);
|
||||
this._editStackElementsMap = new Map<string, EditStackElement>();
|
||||
this._editStackElementsMap = new Map<string, SingleModelEditStackElement>();
|
||||
for (const editStackElement of this._editStackElementsArr) {
|
||||
const key = uriGetComparisonKey(editStackElement.model.uri);
|
||||
const key = uriGetComparisonKey(editStackElement.resource);
|
||||
this._editStackElementsMap.set(key, editStackElement);
|
||||
}
|
||||
}
|
||||
|
||||
public setModel(model: ITextModel | URI): void {
|
||||
const key = uriGetComparisonKey(URI.isUri(model) ? model : model.uri);
|
||||
if (this._editStackElementsMap.has(key)) {
|
||||
this._editStackElementsMap.get(key)!.setModel(model);
|
||||
}
|
||||
}
|
||||
|
||||
public canAppend(model: ITextModel): boolean {
|
||||
if (!this._isOpen) {
|
||||
return false;
|
||||
@@ -135,11 +282,22 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
|
||||
}
|
||||
}
|
||||
|
||||
public heapSize(resource: URI): number {
|
||||
const key = uriGetComparisonKey(resource);
|
||||
if (this._editStackElementsMap.has(key)) {
|
||||
const editStackElement = this._editStackElementsMap.get(key)!;
|
||||
return editStackElement.heapSize();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public split(): IResourceUndoRedoElement[] {
|
||||
return this._editStackElementsArr;
|
||||
}
|
||||
}
|
||||
|
||||
export type EditStackElement = SingleModelEditStackElement | MultiModelEditStackElement;
|
||||
|
||||
function getModelEOL(model: ITextModel): EndOfLineSequence {
|
||||
const eol = model.getEOL();
|
||||
if (eol === '\n') {
|
||||
@@ -149,11 +307,11 @@ function getModelEOL(model: ITextModel): EndOfLineSequence {
|
||||
}
|
||||
}
|
||||
|
||||
function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement | MultiModelEditStackElement {
|
||||
function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement {
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
return ((element instanceof EditStackElement) || (element instanceof MultiModelEditStackElement));
|
||||
return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement));
|
||||
}
|
||||
|
||||
export class EditStack {
|
||||
@@ -177,12 +335,12 @@ export class EditStack {
|
||||
this._undoRedoService.removeElements(this._model.uri);
|
||||
}
|
||||
|
||||
private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement | MultiModelEditStackElement {
|
||||
private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement {
|
||||
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
|
||||
if (isKnownStackElement(lastElement) && lastElement.canAppend(this._model)) {
|
||||
return lastElement;
|
||||
}
|
||||
const newElement = new EditStackElement(this._model, beforeCursorState);
|
||||
const newElement = new SingleModelEditStackElement(this._model, beforeCursorState);
|
||||
this._undoRedoService.pushElement(newElement);
|
||||
return newElement;
|
||||
}
|
||||
@@ -195,7 +353,7 @@ export class EditStack {
|
||||
|
||||
public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null {
|
||||
const editStackElement = this._getOrCreateEditStackElement(beforeCursorState);
|
||||
const inverseEditOperations = this._model.applyEdits(editOperations);
|
||||
const inverseEditOperations = this._model.applyEdits(editOperations, true);
|
||||
const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
|
||||
editStackElement.append(this._model, inverseEditOperations, getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState);
|
||||
return afterCursorState;
|
||||
|
||||
@@ -269,7 +269,7 @@ export class PieceTreeBase {
|
||||
protected _buffers!: StringBuffer[]; // 0 is change buffer, others are readonly original buffer.
|
||||
protected _lineCnt!: number;
|
||||
protected _length!: number;
|
||||
protected _EOL!: string;
|
||||
protected _EOL!: '\r\n' | '\n';
|
||||
protected _EOLLength!: number;
|
||||
protected _EOLNormalized!: boolean;
|
||||
private _lastChangeBufferPos!: BufferCursor;
|
||||
@@ -351,7 +351,7 @@ export class PieceTreeBase {
|
||||
}
|
||||
|
||||
// #region Buffer API
|
||||
public getEOL(): string {
|
||||
public getEOL(): '\r\n' | '\n' {
|
||||
return this._EOL;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { ApplyEditsResult, EndOfLinePreference, FindMatch, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot, ValidAnnotatedEditOperation, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
|
||||
import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
import { countEOL, StringEOL } from 'vs/editor/common/model/tokensStore';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
|
||||
export interface IValidatedEditOperation {
|
||||
sortIndex: number;
|
||||
@@ -16,7 +18,10 @@ export interface IValidatedEditOperation {
|
||||
range: Range;
|
||||
rangeOffset: number;
|
||||
rangeLength: number;
|
||||
lines: string[] | null;
|
||||
text: string;
|
||||
eolCount: number;
|
||||
firstLineLength: number;
|
||||
lastLineLength: number;
|
||||
forceMoveMarkers: boolean;
|
||||
isAutoWhitespaceEdit: boolean;
|
||||
}
|
||||
@@ -60,7 +65,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
public getBOM(): string {
|
||||
return this._BOM;
|
||||
}
|
||||
public getEOL(): string {
|
||||
public getEOL(): '\r\n' | '\n' {
|
||||
return this._pieceTree.getEOL();
|
||||
}
|
||||
|
||||
@@ -201,7 +206,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
this._pieceTree.setEOL(newEOL);
|
||||
}
|
||||
|
||||
public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult {
|
||||
public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult {
|
||||
let mightContainRTL = this._mightContainRTL;
|
||||
let mightContainNonBasicASCII = this._mightContainNonBasicASCII;
|
||||
let canReduceOperations = true;
|
||||
@@ -220,13 +225,34 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
if (!mightContainNonBasicASCII && op.text) {
|
||||
mightContainNonBasicASCII = !strings.isBasicASCII(op.text);
|
||||
}
|
||||
|
||||
let validText = '';
|
||||
let eolCount = 0;
|
||||
let firstLineLength = 0;
|
||||
let lastLineLength = 0;
|
||||
if (op.text) {
|
||||
let strEOL: StringEOL;
|
||||
[eolCount, firstLineLength, lastLineLength, strEOL] = countEOL(op.text);
|
||||
|
||||
const bufferEOL = this.getEOL();
|
||||
const expectedStrEOL = (bufferEOL === '\r\n' ? StringEOL.CRLF : StringEOL.LF);
|
||||
if (strEOL === StringEOL.Unknown || strEOL === expectedStrEOL) {
|
||||
validText = op.text;
|
||||
} else {
|
||||
validText = op.text.replace(/\r\n|\r|\n/g, bufferEOL);
|
||||
}
|
||||
}
|
||||
|
||||
operations[i] = {
|
||||
sortIndex: i,
|
||||
identifier: op.identifier || null,
|
||||
range: validatedRange,
|
||||
rangeOffset: this.getOffsetAt(validatedRange.startLineNumber, validatedRange.startColumn),
|
||||
rangeLength: this.getValueLengthInRange(validatedRange),
|
||||
lines: op.text ? op.text.split(/\r\n|\r|\n/) : null,
|
||||
text: validText,
|
||||
eolCount: eolCount,
|
||||
firstLineLength: firstLineLength,
|
||||
lastLineLength: lastLineLength,
|
||||
forceMoveMarkers: Boolean(op.forceMoveMarkers),
|
||||
isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false
|
||||
};
|
||||
@@ -254,46 +280,56 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
}
|
||||
|
||||
// Delta encode operations
|
||||
let reverseRanges = PieceTreeTextBuffer._getInverseEditRanges(operations);
|
||||
let reverseRanges = (computeUndoEdits || recordTrimAutoWhitespace ? PieceTreeTextBuffer._getInverseEditRanges(operations) : []);
|
||||
let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = [];
|
||||
if (recordTrimAutoWhitespace) {
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
let op = operations[i];
|
||||
let reverseRange = reverseRanges[i];
|
||||
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
let op = operations[i];
|
||||
let reverseRange = reverseRanges[i];
|
||||
|
||||
if (recordTrimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) {
|
||||
// Record already the future line numbers that might be auto whitespace removal candidates on next edit
|
||||
for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) {
|
||||
let currentLineContent = '';
|
||||
if (lineNumber === reverseRange.startLineNumber) {
|
||||
currentLineContent = this.getLineContent(op.range.startLineNumber);
|
||||
if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) {
|
||||
continue;
|
||||
if (op.isAutoWhitespaceEdit && op.range.isEmpty()) {
|
||||
// Record already the future line numbers that might be auto whitespace removal candidates on next edit
|
||||
for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) {
|
||||
let currentLineContent = '';
|
||||
if (lineNumber === reverseRange.startLineNumber) {
|
||||
currentLineContent = this.getLineContent(op.range.startLineNumber);
|
||||
if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent });
|
||||
}
|
||||
newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let reverseOperations: IReverseSingleEditOperation[] = [];
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
let op = operations[i];
|
||||
let reverseRange = reverseRanges[i];
|
||||
let reverseOperations: IReverseSingleEditOperation[] | null = null;
|
||||
if (computeUndoEdits) {
|
||||
|
||||
reverseOperations[i] = {
|
||||
sortIndex: op.sortIndex,
|
||||
identifier: op.identifier,
|
||||
range: reverseRange,
|
||||
text: this.getValueInRange(op.range),
|
||||
forceMoveMarkers: op.forceMoveMarkers
|
||||
};
|
||||
let reverseRangeDeltaOffset = 0;
|
||||
reverseOperations = [];
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const op = operations[i];
|
||||
const reverseRange = reverseRanges[i];
|
||||
const bufferText = this.getValueInRange(op.range);
|
||||
const reverseRangeOffset = op.rangeOffset + reverseRangeDeltaOffset;
|
||||
reverseRangeDeltaOffset += (op.text.length - bufferText.length);
|
||||
|
||||
reverseOperations[i] = {
|
||||
sortIndex: op.sortIndex,
|
||||
identifier: op.identifier,
|
||||
range: reverseRange,
|
||||
text: bufferText,
|
||||
textChange: new TextChange(op.rangeOffset, bufferText, reverseRangeOffset, op.text)
|
||||
};
|
||||
}
|
||||
|
||||
// Can only sort reverse operations when the order is not significant
|
||||
if (!hasTouchingRanges) {
|
||||
reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Can only sort reverse operations when the order is not significant
|
||||
if (!hasTouchingRanges) {
|
||||
reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex);
|
||||
}
|
||||
|
||||
this._mightContainRTL = mightContainRTL;
|
||||
this._mightContainNonBasicASCII = mightContainNonBasicASCII;
|
||||
@@ -350,58 +386,45 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
}
|
||||
|
||||
_toSingleEditOperation(operations: IValidatedEditOperation[]): IValidatedEditOperation {
|
||||
let forceMoveMarkers = false,
|
||||
firstEditRange = operations[0].range,
|
||||
lastEditRange = operations[operations.length - 1].range,
|
||||
entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn),
|
||||
lastEndLineNumber = firstEditRange.startLineNumber,
|
||||
lastEndColumn = firstEditRange.startColumn,
|
||||
result: string[] = [];
|
||||
let forceMoveMarkers = false;
|
||||
const firstEditRange = operations[0].range;
|
||||
const lastEditRange = operations[operations.length - 1].range;
|
||||
const entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn);
|
||||
let lastEndLineNumber = firstEditRange.startLineNumber;
|
||||
let lastEndColumn = firstEditRange.startColumn;
|
||||
const result: string[] = [];
|
||||
|
||||
for (let i = 0, len = operations.length; i < len; i++) {
|
||||
let operation = operations[i],
|
||||
range = operation.range;
|
||||
const operation = operations[i];
|
||||
const range = operation.range;
|
||||
|
||||
forceMoveMarkers = forceMoveMarkers || operation.forceMoveMarkers;
|
||||
|
||||
// (1) -- Push old text
|
||||
for (let lineNumber = lastEndLineNumber; lineNumber < range.startLineNumber; lineNumber++) {
|
||||
if (lineNumber === lastEndLineNumber) {
|
||||
result.push(this.getLineContent(lineNumber).substring(lastEndColumn - 1));
|
||||
} else {
|
||||
result.push('\n');
|
||||
result.push(this.getLineContent(lineNumber));
|
||||
}
|
||||
}
|
||||
|
||||
if (range.startLineNumber === lastEndLineNumber) {
|
||||
result.push(this.getLineContent(range.startLineNumber).substring(lastEndColumn - 1, range.startColumn - 1));
|
||||
} else {
|
||||
result.push('\n');
|
||||
result.push(this.getLineContent(range.startLineNumber).substring(0, range.startColumn - 1));
|
||||
}
|
||||
result.push(this.getValueInRange(new Range(lastEndLineNumber, lastEndColumn, range.startLineNumber, range.startColumn)));
|
||||
|
||||
// (2) -- Push new text
|
||||
if (operation.lines) {
|
||||
for (let j = 0, lenJ = operation.lines.length; j < lenJ; j++) {
|
||||
if (j !== 0) {
|
||||
result.push('\n');
|
||||
}
|
||||
result.push(operation.lines[j]);
|
||||
}
|
||||
if (operation.text.length > 0) {
|
||||
result.push(operation.text);
|
||||
}
|
||||
|
||||
lastEndLineNumber = operation.range.endLineNumber;
|
||||
lastEndColumn = operation.range.endColumn;
|
||||
lastEndLineNumber = range.endLineNumber;
|
||||
lastEndColumn = range.endColumn;
|
||||
}
|
||||
|
||||
const text = result.join('');
|
||||
const [eolCount, firstLineLength, lastLineLength] = countEOL(text);
|
||||
|
||||
return {
|
||||
sortIndex: 0,
|
||||
identifier: operations[0].identifier,
|
||||
range: entireEditRange,
|
||||
rangeOffset: this.getOffsetAt(entireEditRange.startLineNumber, entireEditRange.startColumn),
|
||||
rangeLength: this.getValueLengthInRange(entireEditRange, EndOfLinePreference.TextDefined),
|
||||
lines: result.join('').split('\n'),
|
||||
text: text,
|
||||
eolCount: eolCount,
|
||||
firstLineLength: firstLineLength,
|
||||
lastLineLength: lastLineLength,
|
||||
forceMoveMarkers: forceMoveMarkers,
|
||||
isAutoWhitespaceEdit: false
|
||||
};
|
||||
@@ -421,41 +444,26 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
const endLineNumber = op.range.endLineNumber;
|
||||
const endColumn = op.range.endColumn;
|
||||
|
||||
if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.lines || op.lines.length === 0)) {
|
||||
if (startLineNumber === endLineNumber && startColumn === endColumn && op.text.length === 0) {
|
||||
// no-op
|
||||
continue;
|
||||
}
|
||||
|
||||
const deletingLinesCnt = endLineNumber - startLineNumber;
|
||||
const insertingLinesCnt = (op.lines ? op.lines.length - 1 : 0);
|
||||
const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);
|
||||
|
||||
const text = (op.lines ? op.lines.join(this.getEOL()) : '');
|
||||
|
||||
if (text) {
|
||||
if (op.text) {
|
||||
// replacement
|
||||
this._pieceTree.delete(op.rangeOffset, op.rangeLength);
|
||||
this._pieceTree.insert(op.rangeOffset, text, true);
|
||||
this._pieceTree.insert(op.rangeOffset, op.text, true);
|
||||
|
||||
} else {
|
||||
// deletion
|
||||
this._pieceTree.delete(op.rangeOffset, op.rangeLength);
|
||||
}
|
||||
|
||||
if (editingLinesCnt < insertingLinesCnt) {
|
||||
let newLinesContent: string[] = [];
|
||||
for (let j = editingLinesCnt + 1; j <= insertingLinesCnt; j++) {
|
||||
newLinesContent.push(op.lines![j]);
|
||||
}
|
||||
|
||||
newLinesContent[newLinesContent.length - 1] = this.getLineContent(startLineNumber + insertingLinesCnt - 1);
|
||||
}
|
||||
|
||||
const contentChangeRange = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
|
||||
contentChanges.push({
|
||||
range: contentChangeRange,
|
||||
rangeLength: op.rangeLength,
|
||||
text: text,
|
||||
text: op.text,
|
||||
rangeOffset: op.rangeOffset,
|
||||
forceMoveMarkers: op.forceMoveMarkers
|
||||
});
|
||||
@@ -504,18 +512,16 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
|
||||
let resultRange: Range;
|
||||
|
||||
if (op.lines && op.lines.length > 0) {
|
||||
if (op.text.length > 0) {
|
||||
// the operation inserts something
|
||||
let lineCount = op.lines.length;
|
||||
let firstLine = op.lines[0];
|
||||
let lastLine = op.lines[lineCount - 1];
|
||||
const lineCount = op.eolCount + 1;
|
||||
|
||||
if (lineCount === 1) {
|
||||
// single line insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLine.length);
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + op.firstLineLength);
|
||||
} else {
|
||||
// multi line insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLine.length + 1);
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, op.lastLineLength + 1);
|
||||
}
|
||||
} else {
|
||||
// There is nothing to insert
|
||||
|
||||
326
src/vs/editor/common/model/textChange.ts
Normal file
326
src/vs/editor/common/model/textChange.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as buffer from 'vs/base/common/buffer';
|
||||
import { decodeUTF16LE } from 'vs/editor/common/core/stringBuilder';
|
||||
|
||||
export class TextChange {
|
||||
|
||||
public get oldLength(): number {
|
||||
return this.oldText.length;
|
||||
}
|
||||
|
||||
public get oldEnd(): number {
|
||||
return this.oldPosition + this.oldText.length;
|
||||
}
|
||||
|
||||
public get newLength(): number {
|
||||
return this.newText.length;
|
||||
}
|
||||
|
||||
public get newEnd(): number {
|
||||
return this.newPosition + this.newText.length;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly oldPosition: number,
|
||||
public readonly oldText: string,
|
||||
public readonly newPosition: number,
|
||||
public readonly newText: string
|
||||
) { }
|
||||
|
||||
private static _writeStringSize(str: string): number {
|
||||
return (
|
||||
4 + 2 * str.length
|
||||
);
|
||||
}
|
||||
|
||||
private static _writeString(b: Uint8Array, str: string, offset: number): number {
|
||||
const len = str.length;
|
||||
buffer.writeUInt32BE(b, len, offset); offset += 4;
|
||||
for (let i = 0; i < len; i++) {
|
||||
buffer.writeUInt16LE(b, str.charCodeAt(i), offset); offset += 2;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static _readString(b: Uint8Array, offset: number): string {
|
||||
const len = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
return decodeUTF16LE(b, offset, len);
|
||||
}
|
||||
|
||||
public writeSize(): number {
|
||||
return (
|
||||
+ 4 // oldPosition
|
||||
+ 4 // newPosition
|
||||
+ TextChange._writeStringSize(this.oldText)
|
||||
+ TextChange._writeStringSize(this.newText)
|
||||
);
|
||||
}
|
||||
|
||||
public write(b: Uint8Array, offset: number): number {
|
||||
buffer.writeUInt32BE(b, this.oldPosition, offset); offset += 4;
|
||||
buffer.writeUInt32BE(b, this.newPosition, offset); offset += 4;
|
||||
offset = TextChange._writeString(b, this.oldText, offset);
|
||||
offset = TextChange._writeString(b, this.newText, offset);
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static read(b: Uint8Array, offset: number, dest: TextChange[]): number {
|
||||
const oldPosition = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const newPosition = buffer.readUInt32BE(b, offset); offset += 4;
|
||||
const oldText = TextChange._readString(b, offset); offset += TextChange._writeStringSize(oldText);
|
||||
const newText = TextChange._readString(b, offset); offset += TextChange._writeStringSize(newText);
|
||||
dest.push(new TextChange(oldPosition, oldText, newPosition, newText));
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
export function compressConsecutiveTextChanges(prevEdits: TextChange[] | null, currEdits: TextChange[]): TextChange[] {
|
||||
if (prevEdits === null || prevEdits.length === 0) {
|
||||
return currEdits;
|
||||
}
|
||||
const compressor = new TextChangeCompressor(prevEdits, currEdits);
|
||||
return compressor.compress();
|
||||
}
|
||||
|
||||
class TextChangeCompressor {
|
||||
|
||||
private _prevEdits: TextChange[];
|
||||
private _currEdits: TextChange[];
|
||||
|
||||
private _result: TextChange[];
|
||||
private _resultLen: number;
|
||||
|
||||
private _prevLen: number;
|
||||
private _prevDeltaOffset: number;
|
||||
|
||||
private _currLen: number;
|
||||
private _currDeltaOffset: number;
|
||||
|
||||
constructor(prevEdits: TextChange[], currEdits: TextChange[]) {
|
||||
this._prevEdits = prevEdits;
|
||||
this._currEdits = currEdits;
|
||||
|
||||
this._result = [];
|
||||
this._resultLen = 0;
|
||||
|
||||
this._prevLen = this._prevEdits.length;
|
||||
this._prevDeltaOffset = 0;
|
||||
|
||||
this._currLen = this._currEdits.length;
|
||||
this._currDeltaOffset = 0;
|
||||
}
|
||||
|
||||
public compress(): TextChange[] {
|
||||
let prevIndex = 0;
|
||||
let currIndex = 0;
|
||||
|
||||
let prevEdit = this._getPrev(prevIndex);
|
||||
let currEdit = this._getCurr(currIndex);
|
||||
|
||||
while (prevIndex < this._prevLen || currIndex < this._currLen) {
|
||||
|
||||
if (prevEdit === null) {
|
||||
this._acceptCurr(currEdit!);
|
||||
currEdit = this._getCurr(++currIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currEdit === null) {
|
||||
this._acceptPrev(prevEdit);
|
||||
prevEdit = this._getPrev(++prevIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currEdit.oldEnd <= prevEdit.newPosition) {
|
||||
this._acceptCurr(currEdit);
|
||||
currEdit = this._getCurr(++currIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prevEdit.newEnd <= currEdit.oldPosition) {
|
||||
this._acceptPrev(prevEdit);
|
||||
prevEdit = this._getPrev(++prevIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currEdit.oldPosition < prevEdit.newPosition) {
|
||||
const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newPosition - currEdit.oldPosition);
|
||||
this._acceptCurr(e1);
|
||||
currEdit = e2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prevEdit.newPosition < currEdit.oldPosition) {
|
||||
const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldPosition - prevEdit.newPosition);
|
||||
this._acceptPrev(e1);
|
||||
prevEdit = e2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, currEdit.oldPosition === prevEdit.newPosition
|
||||
|
||||
let mergePrev: TextChange;
|
||||
let mergeCurr: TextChange;
|
||||
|
||||
if (currEdit.oldEnd === prevEdit.newEnd) {
|
||||
mergePrev = prevEdit;
|
||||
mergeCurr = currEdit;
|
||||
prevEdit = this._getPrev(++prevIndex);
|
||||
currEdit = this._getCurr(++currIndex);
|
||||
} else if (currEdit.oldEnd < prevEdit.newEnd) {
|
||||
const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldLength);
|
||||
mergePrev = e1;
|
||||
mergeCurr = currEdit;
|
||||
prevEdit = e2;
|
||||
currEdit = this._getCurr(++currIndex);
|
||||
} else {
|
||||
const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newLength);
|
||||
mergePrev = prevEdit;
|
||||
mergeCurr = e1;
|
||||
prevEdit = this._getPrev(++prevIndex);
|
||||
currEdit = e2;
|
||||
}
|
||||
|
||||
this._result[this._resultLen++] = new TextChange(
|
||||
mergePrev.oldPosition,
|
||||
mergePrev.oldText,
|
||||
mergeCurr.newPosition,
|
||||
mergeCurr.newText
|
||||
);
|
||||
this._prevDeltaOffset += mergePrev.newLength - mergePrev.oldLength;
|
||||
this._currDeltaOffset += mergeCurr.newLength - mergeCurr.oldLength;
|
||||
}
|
||||
|
||||
const merged = TextChangeCompressor._merge(this._result);
|
||||
const cleaned = TextChangeCompressor._removeNoOps(merged);
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private _acceptCurr(currEdit: TextChange): void {
|
||||
this._result[this._resultLen++] = TextChangeCompressor._rebaseCurr(this._prevDeltaOffset, currEdit);
|
||||
this._currDeltaOffset += currEdit.newLength - currEdit.oldLength;
|
||||
}
|
||||
|
||||
private _getCurr(currIndex: number): TextChange | null {
|
||||
return (currIndex < this._currLen ? this._currEdits[currIndex] : null);
|
||||
}
|
||||
|
||||
private _acceptPrev(prevEdit: TextChange): void {
|
||||
this._result[this._resultLen++] = TextChangeCompressor._rebasePrev(this._currDeltaOffset, prevEdit);
|
||||
this._prevDeltaOffset += prevEdit.newLength - prevEdit.oldLength;
|
||||
}
|
||||
|
||||
private _getPrev(prevIndex: number): TextChange | null {
|
||||
return (prevIndex < this._prevLen ? this._prevEdits[prevIndex] : null);
|
||||
}
|
||||
|
||||
private static _rebaseCurr(prevDeltaOffset: number, currEdit: TextChange): TextChange {
|
||||
return new TextChange(
|
||||
currEdit.oldPosition - prevDeltaOffset,
|
||||
currEdit.oldText,
|
||||
currEdit.newPosition,
|
||||
currEdit.newText
|
||||
);
|
||||
}
|
||||
|
||||
private static _rebasePrev(currDeltaOffset: number, prevEdit: TextChange): TextChange {
|
||||
return new TextChange(
|
||||
prevEdit.oldPosition,
|
||||
prevEdit.oldText,
|
||||
prevEdit.newPosition + currDeltaOffset,
|
||||
prevEdit.newText
|
||||
);
|
||||
}
|
||||
|
||||
private static _splitPrev(edit: TextChange, offset: number): [TextChange, TextChange] {
|
||||
const preText = edit.newText.substr(0, offset);
|
||||
const postText = edit.newText.substr(offset);
|
||||
|
||||
return [
|
||||
new TextChange(
|
||||
edit.oldPosition,
|
||||
edit.oldText,
|
||||
edit.newPosition,
|
||||
preText
|
||||
),
|
||||
new TextChange(
|
||||
edit.oldEnd,
|
||||
'',
|
||||
edit.newPosition + offset,
|
||||
postText
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
private static _splitCurr(edit: TextChange, offset: number): [TextChange, TextChange] {
|
||||
const preText = edit.oldText.substr(0, offset);
|
||||
const postText = edit.oldText.substr(offset);
|
||||
|
||||
return [
|
||||
new TextChange(
|
||||
edit.oldPosition,
|
||||
preText,
|
||||
edit.newPosition,
|
||||
edit.newText
|
||||
),
|
||||
new TextChange(
|
||||
edit.oldPosition + offset,
|
||||
postText,
|
||||
edit.newEnd,
|
||||
''
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
private static _merge(edits: TextChange[]): TextChange[] {
|
||||
if (edits.length === 0) {
|
||||
return edits;
|
||||
}
|
||||
|
||||
let result: TextChange[] = [], resultLen = 0;
|
||||
|
||||
let prev = edits[0];
|
||||
for (let i = 1; i < edits.length; i++) {
|
||||
const curr = edits[i];
|
||||
|
||||
if (prev.oldEnd === curr.oldPosition) {
|
||||
// Merge into `prev`
|
||||
prev = new TextChange(
|
||||
prev.oldPosition,
|
||||
prev.oldText + curr.oldText,
|
||||
prev.newPosition,
|
||||
prev.newText + curr.newText
|
||||
);
|
||||
} else {
|
||||
result[resultLen++] = prev;
|
||||
prev = curr;
|
||||
}
|
||||
}
|
||||
result[resultLen++] = prev;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _removeNoOps(edits: TextChange[]): TextChange[] {
|
||||
if (edits.length === 0) {
|
||||
return edits;
|
||||
}
|
||||
|
||||
let result: TextChange[] = [], resultLen = 0;
|
||||
|
||||
for (let i = 0; i < edits.length; i++) {
|
||||
const edit = edits[i];
|
||||
|
||||
if (edit.oldText === edit.newText) {
|
||||
continue;
|
||||
}
|
||||
result[resultLen++] = edit;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import { Color } from 'vs/base/common/color';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { EditorTheme } from 'vs/editor/common/view/viewContext';
|
||||
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
|
||||
function createTextBufferBuilder() {
|
||||
return new PieceTreeTextBufferBuilder();
|
||||
@@ -367,7 +368,6 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this._onWillDispose.fire();
|
||||
this._languageRegistryListener.dispose();
|
||||
this._tokenization.dispose();
|
||||
this._undoRedoService.removeElements(this.uri);
|
||||
this._isDisposed = true;
|
||||
super.dispose();
|
||||
this._isDisposing = false;
|
||||
@@ -711,7 +711,11 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this._alternativeVersionId = this._versionId;
|
||||
}
|
||||
|
||||
private _overwriteAlternativeVersionId(newAlternativeVersionId: number): void {
|
||||
public _overwriteVersionId(versionId: number): void {
|
||||
this._versionId = versionId;
|
||||
}
|
||||
|
||||
public _overwriteAlternativeVersionId(newAlternativeVersionId: number): void {
|
||||
this._alternativeVersionId = newAlternativeVersionId;
|
||||
}
|
||||
|
||||
@@ -1285,19 +1289,39 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
|
||||
}
|
||||
|
||||
_applyUndoRedoEdits(edits: model.IValidEditOperations[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] {
|
||||
_applyUndo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
|
||||
const edits = changes.map<model.IIdentifiedSingleEditOperation>((change) => {
|
||||
const rangeStart = this.getPositionAt(change.newPosition);
|
||||
const rangeEnd = this.getPositionAt(change.newEnd);
|
||||
return {
|
||||
range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),
|
||||
text: change.oldText
|
||||
};
|
||||
});
|
||||
this._applyUndoRedoEdits(edits, eol, true, false, resultingAlternativeVersionId, resultingSelection);
|
||||
}
|
||||
|
||||
_applyRedo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
|
||||
const edits = changes.map<model.IIdentifiedSingleEditOperation>((change) => {
|
||||
const rangeStart = this.getPositionAt(change.oldPosition);
|
||||
const rangeEnd = this.getPositionAt(change.oldEnd);
|
||||
return {
|
||||
range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),
|
||||
text: change.newText
|
||||
};
|
||||
});
|
||||
this._applyUndoRedoEdits(edits, eol, false, true, resultingAlternativeVersionId, resultingSelection);
|
||||
}
|
||||
|
||||
private _applyUndoRedoEdits(edits: model.IIdentifiedSingleEditOperation[], 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;
|
||||
let reverseEdits: model.IValidEditOperations[] = [];
|
||||
for (let i = 0, len = edits.length; i < len; i++) {
|
||||
reverseEdits[i] = { operations: this.applyEdits(edits[i].operations) };
|
||||
}
|
||||
this.applyEdits(edits, false);
|
||||
this.setEOL(eol);
|
||||
this._overwriteAlternativeVersionId(resultingAlternativeVersionId);
|
||||
return reverseEdits;
|
||||
} finally {
|
||||
this._isUndoing = false;
|
||||
this._isRedoing = false;
|
||||
@@ -1306,21 +1330,25 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
}
|
||||
|
||||
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] {
|
||||
public applyEdits(operations: model.IIdentifiedSingleEditOperation[]): void;
|
||||
public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: false): void;
|
||||
public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: true): model.IValidEditOperation[];
|
||||
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: boolean = false): void | model.IValidEditOperation[] {
|
||||
try {
|
||||
this._onDidChangeDecorations.beginDeferredEmit();
|
||||
this._eventEmitter.beginDeferredEmit();
|
||||
return this._doApplyEdits(this._validateEditOperations(rawOperations));
|
||||
const operations = this._validateEditOperations(rawOperations);
|
||||
return this._doApplyEdits(operations, computeUndoEdits);
|
||||
} finally {
|
||||
this._eventEmitter.endDeferredEmit();
|
||||
this._onDidChangeDecorations.endDeferredEmit();
|
||||
}
|
||||
}
|
||||
|
||||
private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
|
||||
private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[], computeUndoEdits: boolean): void | model.IValidEditOperation[] {
|
||||
|
||||
const oldLineCount = this._buffer.getLineCount();
|
||||
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace);
|
||||
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace, computeUndoEdits);
|
||||
const newLineCount = this._buffer.getLineCount();
|
||||
|
||||
const contentChanges = result.changes;
|
||||
@@ -1395,7 +1423,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
);
|
||||
}
|
||||
|
||||
return result.reverseEdits;
|
||||
return (result.reverseEdits === null ? undefined : result.reverseEdits);
|
||||
}
|
||||
|
||||
public undo(): void {
|
||||
|
||||
@@ -11,10 +11,18 @@ import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType, Toke
|
||||
import { writeUInt32BE, readUInt32BE } from 'vs/base/common/buffer';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export function countEOL(text: string): [number, number, number] {
|
||||
export const enum StringEOL {
|
||||
Unknown = 0,
|
||||
Invalid = 3,
|
||||
LF = 1,
|
||||
CRLF = 2
|
||||
}
|
||||
|
||||
export function countEOL(text: string): [number, number, number, StringEOL] {
|
||||
let eolCount = 0;
|
||||
let firstLineLength = 0;
|
||||
let lastLineStart = 0;
|
||||
let eol: StringEOL = StringEOL.Unknown;
|
||||
for (let i = 0, len = text.length; i < len; i++) {
|
||||
const chr = text.charCodeAt(i);
|
||||
|
||||
@@ -25,12 +33,16 @@ export function countEOL(text: string): [number, number, number] {
|
||||
eolCount++;
|
||||
if (i + 1 < len && text.charCodeAt(i + 1) === CharCode.LineFeed) {
|
||||
// \r\n... case
|
||||
eol |= StringEOL.CRLF;
|
||||
i++; // skip \n
|
||||
} else {
|
||||
// \r... case
|
||||
eol |= StringEOL.Invalid;
|
||||
}
|
||||
lastLineStart = i + 1;
|
||||
} else if (chr === CharCode.LineFeed) {
|
||||
// \n... case
|
||||
eol |= StringEOL.LF;
|
||||
if (eolCount === 0) {
|
||||
firstLineLength = i;
|
||||
}
|
||||
@@ -41,7 +53,7 @@ export function countEOL(text: string): [number, number, number] {
|
||||
if (eolCount === 0) {
|
||||
firstLineLength = text.length;
|
||||
}
|
||||
return [eolCount, firstLineLength, text.length - lastLineStart];
|
||||
return [eolCount, firstLineLength, text.length - lastLineStart, eol];
|
||||
}
|
||||
|
||||
function getDefaultMetadata(topLevelLanguageId: LanguageId): number {
|
||||
|
||||
@@ -1396,6 +1396,15 @@ export interface AuthenticationSession {
|
||||
accountName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface AuthenticationSessionsChangeEvent {
|
||||
added: string[];
|
||||
removed: string[];
|
||||
changed: string[];
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
id: string;
|
||||
title: string;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -25,7 +26,14 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { IUndoRedoService, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { StringSHA1 } from 'vs/base/common/hash';
|
||||
import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement } from 'vs/editor/common/model/editStack';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
|
||||
export const MAINTAIN_UNDO_REDO_STACK = true;
|
||||
|
||||
export interface IEditorSemanticHighlightingOptions {
|
||||
enabled?: boolean;
|
||||
@@ -35,6 +43,18 @@ function MODEL_ID(resource: URI): string {
|
||||
return resource.toString();
|
||||
}
|
||||
|
||||
function computeModelSha1(model: ITextModel): string {
|
||||
// compute the sha1
|
||||
const shaComputer = new StringSHA1();
|
||||
const snapshot = model.createSnapshot();
|
||||
let text: string | null;
|
||||
while ((text = snapshot.read())) {
|
||||
shaComputer.update(text);
|
||||
}
|
||||
return shaComputer.digest();
|
||||
}
|
||||
|
||||
|
||||
class ModelData implements IDisposable {
|
||||
public readonly model: ITextModel;
|
||||
|
||||
@@ -98,13 +118,42 @@ interface IRawConfig {
|
||||
|
||||
const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? DefaultEndOfLine.LF : DefaultEndOfLine.CRLF;
|
||||
|
||||
export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
public _serviceBrand: undefined;
|
||||
interface EditStackPastFutureElements {
|
||||
past: EditStackElement[];
|
||||
future: EditStackElement[];
|
||||
}
|
||||
|
||||
private readonly _configurationService: IConfigurationService;
|
||||
private readonly _configurationServiceSubscription: IDisposable;
|
||||
private readonly _resourcePropertiesService: ITextResourcePropertiesService;
|
||||
private readonly _undoRedoService: IUndoRedoService;
|
||||
function isEditStackPastFutureElements(undoElements: IPastFutureElements): undoElements is EditStackPastFutureElements {
|
||||
return (isEditStackElements(undoElements.past) && isEditStackElements(undoElements.future));
|
||||
}
|
||||
|
||||
function isEditStackElements(elements: IUndoRedoElement[]): elements is EditStackElement[] {
|
||||
for (const element of elements) {
|
||||
if (element instanceof SingleModelEditStackElement) {
|
||||
continue;
|
||||
}
|
||||
if (element instanceof MultiModelEditStackElement) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class DisposedModelInfo {
|
||||
constructor(
|
||||
public readonly uri: URI,
|
||||
public readonly sha1: string,
|
||||
public readonly versionId: number,
|
||||
public readonly alternativeVersionId: number,
|
||||
) { }
|
||||
}
|
||||
|
||||
export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
private static _PROMPT_UNDO_REDO_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
|
||||
public readonly onModelAdded: Event<ITextModel> = this._onModelAdded.event;
|
||||
@@ -115,39 +164,37 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
private readonly _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }> = this._register(new Emitter<{ model: ITextModel; oldModeId: string; }>());
|
||||
public readonly onModelModeChanged: Event<{ model: ITextModel; oldModeId: string; }> = this._onModelModeChanged.event;
|
||||
|
||||
private _modelCreationOptionsByLanguageAndResource: {
|
||||
[languageAndResource: string]: ITextModelCreationOptions;
|
||||
};
|
||||
private _modelCreationOptionsByLanguageAndResource: { [languageAndResource: string]: ITextModelCreationOptions; };
|
||||
|
||||
/**
|
||||
* All the models known in the system.
|
||||
*/
|
||||
private readonly _models: { [modelId: string]: ModelData; };
|
||||
private readonly _disposedModels: Map<string, DisposedModelInfo>;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ILogService logService: ILogService,
|
||||
@IUndoRedoService undoRedoService: IUndoRedoService
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ITextResourcePropertiesService private readonly _resourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
) {
|
||||
super();
|
||||
this._configurationService = configurationService;
|
||||
this._resourcePropertiesService = resourcePropertiesService;
|
||||
this._undoRedoService = undoRedoService;
|
||||
this._models = {};
|
||||
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
|
||||
this._models = {};
|
||||
this._disposedModels = new Map<string, DisposedModelInfo>();
|
||||
|
||||
this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions());
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()));
|
||||
this._updateModelOptions();
|
||||
|
||||
this._register(new SemanticColoringFeature(this, themeService, configurationService, logService));
|
||||
this._register(new SemanticColoringFeature(this, this._themeService, this._configurationService, this._logService));
|
||||
}
|
||||
|
||||
private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
|
||||
let tabSize = EDITOR_MODEL_DEFAULTS.tabSize;
|
||||
if (config.editor && typeof config.editor.tabSize !== 'undefined') {
|
||||
let parsedTabSize = parseInt(config.editor.tabSize, 10);
|
||||
const parsedTabSize = parseInt(config.editor.tabSize, 10);
|
||||
if (!isNaN(parsedTabSize)) {
|
||||
tabSize = parsedTabSize;
|
||||
}
|
||||
@@ -158,7 +205,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
let indentSize = tabSize;
|
||||
if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') {
|
||||
let parsedIndentSize = parseInt(config.editor.indentSize, 10);
|
||||
const parsedIndentSize = parseInt(config.editor.indentSize, 10);
|
||||
if (!isNaN(parsedIndentSize)) {
|
||||
indentSize = parsedIndentSize;
|
||||
}
|
||||
@@ -230,14 +277,14 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
}
|
||||
|
||||
private _updateModelOptions(): void {
|
||||
let oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource;
|
||||
const oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource;
|
||||
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
|
||||
|
||||
// Update options on all models
|
||||
let keys = Object.keys(this._models);
|
||||
const keys = Object.keys(this._models);
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let modelId = keys[i];
|
||||
let modelData = this._models[modelId];
|
||||
const modelId = keys[i];
|
||||
const modelData = this._models[modelId];
|
||||
const language = modelData.model.getLanguageIdentifier().language;
|
||||
const uri = modelData.model.uri;
|
||||
const oldOptions = oldOptionsByLanguageAndResource[language + uri];
|
||||
@@ -277,17 +324,30 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._configurationServiceSubscription.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// --- begin IModelService
|
||||
|
||||
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
|
||||
// create & save the model
|
||||
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
|
||||
const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService);
|
||||
if (resource && this._disposedModels.has(MODEL_ID(resource))) {
|
||||
const disposedModelData = this._disposedModels.get(MODEL_ID(resource))!;
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if (computeModelSha1(model) === disposedModelData.sha1 && isEditStackPastFutureElements(elements)) {
|
||||
for (const element of elements.past) {
|
||||
element.setModel(model);
|
||||
}
|
||||
for (const element of elements.future) {
|
||||
element.setModel(model);
|
||||
}
|
||||
this._undoRedoService.setElementsIsValid(resource, true);
|
||||
model._overwriteVersionId(disposedModelData.versionId);
|
||||
model._overwriteAlternativeVersionId(disposedModelData.alternativeVersionId);
|
||||
} else {
|
||||
this._undoRedoService.removeElements(resource);
|
||||
}
|
||||
}
|
||||
const modelId = MODEL_ID(model.uri);
|
||||
|
||||
if (this._models[modelId]) {
|
||||
@@ -360,7 +420,8 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
const commonSuffix = this._commonSuffix(model, modelLineCount - commonPrefix, commonPrefix, textBuffer, textBufferLineCount - commonPrefix, commonPrefix);
|
||||
|
||||
let oldRange: Range, newRange: Range;
|
||||
let oldRange: Range;
|
||||
let newRange: Range;
|
||||
if (commonSuffix > 0) {
|
||||
oldRange = new Range(commonPrefix + 1, 1, modelLineCount - commonSuffix + 1, 1);
|
||||
newRange = new Range(commonPrefix + 1, 1, textBufferLineCount - commonSuffix + 1, 1);
|
||||
@@ -394,7 +455,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
if (!languageSelection) {
|
||||
return;
|
||||
}
|
||||
let modelData = this._models[MODEL_ID(model.uri)];
|
||||
const modelData = this._models[MODEL_ID(model.uri)];
|
||||
if (!modelData) {
|
||||
return;
|
||||
}
|
||||
@@ -403,19 +464,69 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
public destroyModel(resource: URI): void {
|
||||
// We need to support that not all models get disposed through this service (i.e. model.dispose() should work!)
|
||||
let modelData = this._models[MODEL_ID(resource)];
|
||||
const modelData = this._models[MODEL_ID(resource)];
|
||||
if (!modelData) {
|
||||
return;
|
||||
}
|
||||
const model = modelData.model;
|
||||
let maintainUndoRedoStack = false;
|
||||
let heapSize = 0;
|
||||
if (MAINTAIN_UNDO_REDO_STACK && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote)) {
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if ((elements.past.length > 0 || elements.future.length > 0) && isEditStackPastFutureElements(elements)) {
|
||||
maintainUndoRedoStack = true;
|
||||
for (const element of elements.past) {
|
||||
heapSize += element.heapSize(resource);
|
||||
element.setModel(resource); // remove reference from text buffer instance
|
||||
}
|
||||
for (const element of elements.future) {
|
||||
heapSize += element.heapSize(resource);
|
||||
element.setModel(resource); // remove reference from text buffer instance
|
||||
}
|
||||
} else {
|
||||
maintainUndoRedoStack = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (maintainUndoRedoStack) {
|
||||
// We only invalidate the elements, but they remain in the undo-redo service.
|
||||
this._undoRedoService.setElementsIsValid(resource, false);
|
||||
this._disposedModels.set(MODEL_ID(resource), new DisposedModelInfo(resource, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId()));
|
||||
} else {
|
||||
this._undoRedoService.removeElements(resource);
|
||||
}
|
||||
|
||||
modelData.model.dispose();
|
||||
|
||||
// After disposing the model, prompt and ask if we should keep the undo-redo stack
|
||||
if (maintainUndoRedoStack && heapSize > ModelServiceImpl._PROMPT_UNDO_REDO_SIZE_LIMIT) {
|
||||
const mbSize = (heapSize / 1024 / 1024).toFixed(1);
|
||||
this._dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('undoRedoConfirm', "Keep the undo-redo stack for {0} in memory ({1} MB)?", (resource.scheme === Schemas.file ? resource.fsPath : resource.path), mbSize),
|
||||
[
|
||||
nls.localize('nok', "Discard"),
|
||||
nls.localize('ok', "Keep"),
|
||||
],
|
||||
{
|
||||
cancelId: 2
|
||||
}
|
||||
).then((result) => {
|
||||
const discard = (result.choice === 2 || result.choice === 0);
|
||||
if (discard) {
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
this._undoRedoService.removeElements(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getModels(): ITextModel[] {
|
||||
let ret: ITextModel[] = [];
|
||||
const ret: ITextModel[] = [];
|
||||
|
||||
let keys = Object.keys(this._models);
|
||||
const keys = Object.keys(this._models);
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let modelId = keys[i];
|
||||
const modelId = keys[i];
|
||||
ret.push(this._models[modelId].model);
|
||||
}
|
||||
|
||||
@@ -423,8 +534,8 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
}
|
||||
|
||||
public getModel(resource: URI): ITextModel | null {
|
||||
let modelId = MODEL_ID(resource);
|
||||
let modelData = this._models[modelId];
|
||||
const modelId = MODEL_ID(resource);
|
||||
const modelData = this._models[modelId];
|
||||
if (!modelData) {
|
||||
return null;
|
||||
}
|
||||
@@ -434,8 +545,8 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
// --- end IModelService
|
||||
|
||||
private _onWillDispose(model: ITextModel): void {
|
||||
let modelId = MODEL_ID(model.uri);
|
||||
let modelData = this._models[modelId];
|
||||
const modelId = MODEL_ID(model.uri);
|
||||
const modelData = this._models[modelId];
|
||||
|
||||
delete this._models[modelId];
|
||||
modelData.dispose();
|
||||
|
||||
@@ -36,7 +36,7 @@ export namespace InspectTokensNLS {
|
||||
}
|
||||
|
||||
export namespace GoToLineNLS {
|
||||
export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line...");
|
||||
export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line/Column...");
|
||||
}
|
||||
|
||||
export namespace QuickHelpNLS {
|
||||
|
||||
Reference in New Issue
Block a user