diff --git a/src/vs/editor/browser/viewParts/decorations/decorations.ts b/src/vs/editor/browser/viewParts/decorations/decorations.ts
index 2cc107d4037d7..dc63ec06edc51 100644
--- a/src/vs/editor/browser/viewParts/decorations/decorations.ts
+++ b/src/vs/editor/browser/viewParts/decorations/decorations.ts
@@ -78,15 +78,15 @@ export class DecorationsOverlay extends DynamicViewOverlay {
let decorations: ViewModelDecoration[] = [], decorationsLen = 0;
for (let i = 0, len = _decorations.length; i < len; i++) {
let d = _decorations[i];
- if (d.source.options.className) {
+ if (d.options.className) {
decorations[decorationsLen++] = d;
}
}
// Sort decorations for consistent render output
decorations = decorations.sort((a, b) => {
- let aClassName = a.source.options.className;
- let bClassName = b.source.options.className;
+ let aClassName = a.options.className;
+ let bClassName = b.options.className;
if (aClassName < bClassName) {
return -1;
@@ -120,13 +120,13 @@ export class DecorationsOverlay extends DynamicViewOverlay {
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
let d = decorations[i];
- if (!d.source.options.isWholeLine) {
+ if (!d.options.isWholeLine) {
continue;
}
let decorationOutput = (
'
'
@@ -148,12 +148,12 @@ export class DecorationsOverlay extends DynamicViewOverlay {
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
const d = decorations[i];
- if (d.source.options.isWholeLine) {
+ if (d.options.isWholeLine) {
continue;
}
- const className = d.source.options.className;
- const showIfCollapsed = d.source.options.showIfCollapsed;
+ const className = d.options.className;
+ const showIfCollapsed = d.options.showIfCollapsed;
let range = d.range;
if (showIfCollapsed && range.endColumn === 1 && range.endLineNumber !== range.startLineNumber) {
diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts
index 33874c27b5170..757305ebc08d3 100644
--- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts
+++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts
@@ -145,7 +145,7 @@ export class GlyphMarginOverlay extends DedupOverlay {
let r: DecorationToRender[] = [], rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
let d = decorations[i];
- let glyphMarginClassName = d.source.options.glyphMarginClassName;
+ let glyphMarginClassName = d.options.glyphMarginClassName;
if (glyphMarginClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName);
}
diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts
index 3a143124040d2..437677880960b 100644
--- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts
+++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts
@@ -73,7 +73,7 @@ export class LinesDecorationsOverlay extends DedupOverlay {
let r: DecorationToRender[] = [], rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
let d = decorations[i];
- let linesDecorationsClassName = d.source.options.linesDecorationsClassName;
+ let linesDecorationsClassName = d.options.linesDecorationsClassName;
if (linesDecorationsClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName);
}
diff --git a/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts b/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts
index b83c9faa123ce..75ff0a0e3ed95 100644
--- a/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts
+++ b/src/vs/editor/browser/viewParts/marginDecorations/marginDecorations.ts
@@ -63,7 +63,7 @@ export class MarginViewLineDecorationsOverlay extends DedupOverlay {
let r: DecorationToRender[] = [], rLen = 0;
for (let i = 0, len = decorations.length; i < len; i++) {
let d = decorations[i];
- let marginClassName = d.source.options.marginClassName;
+ let marginClassName = d.options.marginClassName;
if (marginClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, marginClassName);
}
diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts
index 91ebeaf394502..5add770087e43 100644
--- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts
+++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts
@@ -6,262 +6,399 @@
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ViewPart } from 'vs/editor/browser/view/viewPart';
-import { OverviewRulerImpl } from 'vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { Position } from 'vs/editor/common/core/position';
import { TokenizationRegistry } from 'vs/editor/common/modes';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
-import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
import { editorOverviewRulerBorder, editorCursorForeground } from 'vs/editor/common/view/editorColorRegistry';
import { Color } from 'vs/base/common/color';
-import { ThemeColor } from 'vs/platform/theme/common/themeService';
+import { ITheme } from 'vs/platform/theme/common/themeService';
+import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
-export class DecorationsOverviewRuler extends ViewPart {
+class Settings {
- static MIN_DECORATION_HEIGHT = 6;
- static MAX_DECORATION_HEIGHT = 60;
+ public readonly lineHeight: number;
+ public readonly pixelRatio: number;
+ public readonly overviewRulerLanes: number;
- private readonly _tokensColorTrackerListener: IDisposable;
+ public readonly renderBorder: boolean;
+ public readonly borderColor: string;
- private _overviewRuler: OverviewRulerImpl;
+ public readonly hideCursor: boolean;
+ public readonly cursorColor: string;
- private _renderBorder: boolean;
- private _borderColor: string;
- private _cursorColor: string;
+ public readonly themeType: 'light' | 'dark' | 'hc';
+ public readonly backgroundColor: string;
- private _shouldUpdateDecorations: boolean;
- private _shouldUpdateCursorPosition: boolean;
+ public readonly top: number;
+ public readonly right: number;
+ public readonly domWidth: number;
+ public readonly domHeight: number;
+ public readonly canvasWidth: number;
+ public readonly canvasHeight: number;
- private _hideCursor: boolean;
- private _cursorPositions: Position[];
+ public readonly x: number[];
+ public readonly w: number[];
+
+ constructor(config: editorCommon.IConfiguration, theme: ITheme) {
+ this.lineHeight = config.editor.lineHeight;
+ this.pixelRatio = config.editor.pixelRatio;
+ this.overviewRulerLanes = config.editor.viewInfo.overviewRulerLanes;
+
+ this.renderBorder = config.editor.viewInfo.overviewRulerBorder;
+ const borderColor = theme.getColor(editorOverviewRulerBorder);
+ this.borderColor = borderColor ? borderColor.toString() : null;
- private _zonesFromDecorations: OverviewRulerZone[];
- private _zonesFromCursors: OverviewRulerZone[];
+ this.hideCursor = config.editor.viewInfo.hideCursorInOverviewRuler;
+ const cursorColor = theme.getColor(editorCursorForeground);
+ this.cursorColor = cursorColor ? cursorColor.transparent(0.7).toString() : null;
+
+ this.themeType = theme.type;
+
+ const minimapEnabled = config.editor.viewInfo.minimap.enabled;
+ const backgroundColor = (minimapEnabled ? TokenizationRegistry.getDefaultBackground() : null);
+ this.backgroundColor = (backgroundColor ? Color.Format.CSS.formatHex(backgroundColor) : null);
+
+ const position = config.editor.layoutInfo.overviewRuler;
+ this.top = position.top;
+ this.right = position.right;
+ this.domWidth = position.width;
+ this.domHeight = position.height;
+ this.canvasWidth = (this.domWidth * this.pixelRatio) | 0;
+ this.canvasHeight = (this.domHeight * this.pixelRatio) | 0;
+
+ const [x, w] = this._initLanes(1, this.canvasWidth, this.overviewRulerLanes);
+ this.x = x;
+ this.w = w;
+ }
+
+ private _initLanes(canvasLeftOffset: number, canvasWidth: number, laneCount: number): [number[], number[]] {
+ const remainingWidth = canvasWidth - canvasLeftOffset;
+
+ if (laneCount >= 3) {
+ const leftWidth = Math.floor(remainingWidth / 3);
+ const rightWidth = Math.floor(remainingWidth / 3);
+ const centerWidth = remainingWidth - leftWidth - rightWidth;
+ const leftOffset = canvasLeftOffset;
+ const centerOffset = leftOffset + leftWidth;
+ const rightOffset = leftOffset + leftWidth + centerWidth;
+
+ return [
+ [
+ 0,
+ leftOffset, // Left
+ centerOffset, // Center
+ leftOffset, // Left | Center
+ rightOffset, // Right
+ leftOffset, // Left | Right
+ centerOffset, // Center | Right
+ leftOffset, // Left | Center | Right
+ ], [
+ 0,
+ leftWidth, // Left
+ centerWidth, // Center
+ leftWidth + centerWidth, // Left | Center
+ rightWidth, // Right
+ leftWidth + centerWidth + rightWidth, // Left | Right
+ centerWidth + rightWidth, // Center | Right
+ leftWidth + centerWidth + rightWidth, // Left | Center | Right
+ ]
+ ];
+ } else if (laneCount === 2) {
+ const leftWidth = Math.floor(remainingWidth / 2);
+ const rightWidth = remainingWidth - leftWidth;
+ const leftOffset = canvasLeftOffset;
+ const rightOffset = leftOffset + leftWidth;
+
+ return [
+ [
+ 0,
+ leftOffset, // Left
+ leftOffset, // Center
+ leftOffset, // Left | Center
+ rightOffset, // Right
+ leftOffset, // Left | Right
+ leftOffset, // Center | Right
+ leftOffset, // Left | Center | Right
+ ], [
+ 0,
+ leftWidth, // Left
+ leftWidth, // Center
+ leftWidth, // Left | Center
+ rightWidth, // Right
+ leftWidth + rightWidth, // Left | Right
+ leftWidth + rightWidth, // Center | Right
+ leftWidth + rightWidth, // Left | Center | Right
+ ]
+ ];
+ } else {
+ const offset = canvasLeftOffset;
+ const width = remainingWidth;
+
+ return [
+ [
+ 0,
+ offset, // Left
+ offset, // Center
+ offset, // Left | Center
+ offset, // Right
+ offset, // Left | Right
+ offset, // Center | Right
+ offset, // Left | Center | Right
+ ], [
+ 0,
+ width, // Left
+ width, // Center
+ width, // Left | Center
+ width, // Right
+ width, // Left | Right
+ width, // Center | Right
+ width, // Left | Center | Right
+ ]
+ ];
+ }
+ }
+
+ public equals(other: Settings): boolean {
+ return (
+ this.lineHeight === other.lineHeight
+ && this.pixelRatio === other.pixelRatio
+ && this.overviewRulerLanes === other.overviewRulerLanes
+ && this.renderBorder === other.renderBorder
+ && this.borderColor === other.borderColor
+ && this.hideCursor === other.hideCursor
+ && this.cursorColor === other.cursorColor
+ && this.themeType === other.themeType
+ && this.backgroundColor === other.backgroundColor
+ && this.top === other.top
+ && this.right === other.right
+ && this.domWidth === other.domWidth
+ && this.domHeight === other.domHeight
+ && this.canvasWidth === other.canvasWidth
+ && this.canvasHeight === other.canvasHeight
+ );
+ }
+}
+
+const enum Constants {
+ MIN_DECORATION_HEIGHT = 6
+}
+
+const enum OverviewRulerLane {
+ Left = 1,
+ Center = 2,
+ Right = 4,
+ Full = 7
+}
+
+export class DecorationsOverviewRuler extends ViewPart {
+
+ private readonly _tokensColorTrackerListener: IDisposable;
+ private readonly _domNode: FastDomNode;
+ private _settings: Settings;
+ private _cursorPositions: Position[];
constructor(context: ViewContext) {
super(context);
- this._overviewRuler = new OverviewRulerImpl(
- 1,
- 'decorationsOverviewRuler',
- this._context.viewLayout.getScrollHeight(),
- this._context.configuration.editor.lineHeight,
- this._context.configuration.editor.pixelRatio,
- DecorationsOverviewRuler.MIN_DECORATION_HEIGHT,
- DecorationsOverviewRuler.MAX_DECORATION_HEIGHT,
- (lineNumber: number) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber)
- );
- this._overviewRuler.setLanesCount(this._context.configuration.editor.viewInfo.overviewRulerLanes, false);
- this._overviewRuler.setLayout(this._context.configuration.editor.layoutInfo.overviewRuler, false);
- this._renderBorder = this._context.configuration.editor.viewInfo.overviewRulerBorder;
+ this._domNode = createFastDomNode(document.createElement('canvas'));
+ this._domNode.setClassName('decorationsOverviewRuler');
+ this._domNode.setPosition('absolute');
+ this._domNode.setLayerHinting(true);
- this._updateColors();
+ this._settings = null;
+ this._updateSettings(false);
- this._updateBackground(false);
this._tokensColorTrackerListener = TokenizationRegistry.onDidChange((e) => {
if (e.changedColorMap) {
- this._updateBackground(true);
+ this._updateSettings(true);
}
});
- this._shouldUpdateDecorations = true;
- this._zonesFromDecorations = [];
-
- this._shouldUpdateCursorPosition = true;
- this._hideCursor = this._context.configuration.editor.viewInfo.hideCursorInOverviewRuler;
-
- this._zonesFromCursors = [];
this._cursorPositions = [];
}
public dispose(): void {
super.dispose();
- this._overviewRuler.dispose();
this._tokensColorTrackerListener.dispose();
}
- private _updateBackground(render: boolean): void {
- const minimapEnabled = this._context.configuration.editor.viewInfo.minimap.enabled;
- this._overviewRuler.setUseBackground((minimapEnabled ? TokenizationRegistry.getDefaultBackground() : null), render);
- }
-
- // ---- begin view event handlers
-
- public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
- let prevLanesCount = this._overviewRuler.getLanesCount();
- let newLanesCount = this._context.configuration.editor.viewInfo.overviewRulerLanes;
-
- if (prevLanesCount !== newLanesCount) {
- this._overviewRuler.setLanesCount(newLanesCount, false);
+ private _updateSettings(renderNow: boolean): boolean {
+ const newSettings = new Settings(this._context.configuration, this._context.theme);
+ if (this._settings !== null && this._settings.equals(newSettings)) {
+ // nothing to do
+ return false;
}
- if (e.lineHeight) {
- this._overviewRuler.setLineHeight(this._context.configuration.editor.lineHeight, false);
- }
+ this._settings = newSettings;
- if (e.pixelRatio) {
- this._overviewRuler.setPixelRatio(this._context.configuration.editor.pixelRatio, false);
- }
+ this._domNode.setTop(this._settings.top);
+ this._domNode.setRight(this._settings.right);
+ this._domNode.setWidth(this._settings.domWidth);
+ this._domNode.setHeight(this._settings.domHeight);
+ this._domNode.domNode.width = this._settings.canvasWidth;
+ this._domNode.domNode.height = this._settings.canvasHeight;
- if (e.viewInfo) {
- this._renderBorder = this._context.configuration.editor.viewInfo.overviewRulerBorder;
- this._hideCursor = this._context.configuration.editor.viewInfo.hideCursorInOverviewRuler;
- this._shouldUpdateCursorPosition = true;
- this._updateBackground(false);
- }
-
- if (e.layoutInfo) {
- this._overviewRuler.setLayout(this._context.configuration.editor.layoutInfo.overviewRuler, false);
+ if (renderNow) {
+ this._render();
}
return true;
}
+ // ---- begin view event handlers
+
+ public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
+ return this._updateSettings(false);
+ }
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
- this._shouldUpdateCursorPosition = true;
this._cursorPositions = [];
for (let i = 0, len = e.selections.length; i < len; i++) {
this._cursorPositions[i] = e.selections[i].getPosition();
}
return true;
}
-
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
- this._shouldUpdateDecorations = true;
return true;
}
-
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
- this._shouldUpdateCursorPosition = true;
- this._shouldUpdateDecorations = true;
return true;
}
-
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
- this._overviewRuler.setScrollHeight(e.scrollHeight, false);
- return super.onScrollChanged(e) || e.scrollHeightChanged;
+ return e.scrollHeightChanged;
}
-
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
-
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
- this._updateColors();
- this._shouldUpdateDecorations = true;
- this._shouldUpdateCursorPosition = true;
- return true;
+ // invalidate color cache
+ this._context.model.invalidateOverviewRulerColorCache();
+ return this._updateSettings(false);
}
// ---- end view event handlers
public getDomNode(): HTMLElement {
- return this._overviewRuler.getDomNode();
+ return this._domNode.domNode;
}
- private _updateColors() {
- let borderColor = this._context.theme.getColor(editorOverviewRulerBorder);
- this._borderColor = borderColor ? borderColor.toString() : null;
-
- let cursorColor = this._context.theme.getColor(editorCursorForeground);
- this._cursorColor = cursorColor ? cursorColor.transparent(0.7).toString() : null;
-
- this._overviewRuler.setThemeType(this._context.theme.type, false);
- }
-
- private _createZonesFromDecorations(): OverviewRulerZone[] {
- let decorations = this._context.model.getAllOverviewRulerDecorations();
- let zones: OverviewRulerZone[] = [];
-
- for (let i = 0, len = decorations.length; i < len; i++) {
- let dec = decorations[i];
- let overviewRuler = dec.source.options.overviewRuler;
- zones[i] = new OverviewRulerZone(
- dec.range.startLineNumber,
- dec.range.endLineNumber,
- overviewRuler.position,
- 0,
- this.resolveRulerColor(overviewRuler.color),
- this.resolveRulerColor(overviewRuler.darkColor),
- this.resolveRulerColor(overviewRuler.hcColor)
- );
- }
-
- return zones;
+ public prepareRender(ctx: RenderingContext): void {
+ // Nothing to read
}
- private resolveRulerColor(color: string | ThemeColor): string {
- if (editorCommon.isThemeColor(color)) {
- let c = this._context.theme.getColor(color.id) || Color.transparent;
- return c.toString();
- }
- return color;
+ public render(editorCtx: RestrictedRenderingContext): void {
+ this._render();
}
- private _createZonesFromCursors(): OverviewRulerZone[] {
- let zones: OverviewRulerZone[] = [];
-
- for (let i = 0, len = this._cursorPositions.length; i < len; i++) {
- let cursor = this._cursorPositions[i];
-
- zones[i] = new OverviewRulerZone(
- cursor.lineNumber,
- cursor.lineNumber,
- editorCommon.OverviewRulerLane.Full,
- 2,
- this._cursorColor,
- this._cursorColor,
- this._cursorColor
- );
+ private _render(): void {
+ const canvasWidth = this._settings.canvasWidth;
+ const canvasHeight = this._settings.canvasHeight;
+ const lineHeight = this._settings.lineHeight;
+ const viewLayout = this._context.viewLayout;
+ const outerHeight = this._context.viewLayout.getScrollHeight();
+ const heightRatio = canvasHeight / outerHeight;
+ const decorations = this._context.model.getAllOverviewRulerDecorations(this._context.theme);
+
+ const minDecorationHeight = (Constants.MIN_DECORATION_HEIGHT * this._settings.pixelRatio) | 0;
+ const halfMinDecorationHeight = (minDecorationHeight / 2) | 0;
+
+ const canvasCtx = this._domNode.domNode.getContext('2d');
+ if (this._settings.backgroundColor === null) {
+ canvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);
+ } else {
+ canvasCtx.fillStyle = this._settings.backgroundColor;
+ canvasCtx.fillRect(0, 0, canvasWidth, canvasHeight);
}
- return zones;
- }
-
- public prepareRender(ctx: RenderingContext): void {
- // Nothing to read
- }
-
- public render(ctx: RestrictedRenderingContext): void {
- if (this._shouldUpdateDecorations || this._shouldUpdateCursorPosition) {
-
- if (this._shouldUpdateDecorations) {
- this._shouldUpdateDecorations = false;
- this._zonesFromDecorations = this._createZonesFromDecorations();
- }
+ const x = this._settings.x;
+ const w = this._settings.w;
+ // Avoid flickering by always rendering the colors in the same order
+ // colors that don't use transparency will be sorted last (they start with #)
+ const colors = Object.keys(decorations);
+ colors.sort();
+ for (let cIndex = 0, cLen = colors.length; cIndex < cLen; cIndex++) {
+ const color = colors[cIndex];
+
+ const colorDecorations = decorations[color];
+
+ canvasCtx.fillStyle = color;
+
+ let prevLane = 0;
+ let prevY1 = 0;
+ let prevY2 = 0;
+ for (let i = 0, len = colorDecorations.length; i < len; i++) {
+ const lane = colorDecorations[3 * i];
+ const startLineNumber = colorDecorations[3 * i + 1];
+ const endLineNumber = colorDecorations[3 * i + 2];
+
+ let y1 = (viewLayout.getVerticalOffsetForLineNumber(startLineNumber) * heightRatio) | 0;
+ let y2 = ((viewLayout.getVerticalOffsetForLineNumber(endLineNumber) + lineHeight) * heightRatio) | 0;
+ let height = y2 - y1;
+ if (height < minDecorationHeight) {
+ let yCenter = ((y1 + y2) / 2) | 0;
+ if (yCenter < halfMinDecorationHeight) {
+ yCenter = halfMinDecorationHeight;
+ } else if (yCenter + halfMinDecorationHeight > canvasHeight) {
+ yCenter = canvasHeight - halfMinDecorationHeight;
+ }
+ y1 = yCenter - halfMinDecorationHeight;
+ y2 = yCenter + halfMinDecorationHeight;
+ }
- if (this._shouldUpdateCursorPosition) {
- this._shouldUpdateCursorPosition = false;
- if (this._hideCursor) {
- this._zonesFromCursors = [];
+ if (y1 > prevY2 + 1 || lane !== prevLane) {
+ // flush prev
+ if (i !== 0) {
+ canvasCtx.fillRect(x[prevLane], prevY1, w[prevLane], prevY2 - prevY1);
+ }
+ prevLane = lane;
+ prevY1 = y1;
+ prevY2 = y2;
} else {
- this._zonesFromCursors = this._createZonesFromCursors();
+ // merge into prev
+ if (y2 > prevY2) {
+ prevY2 = y2;
+ }
}
}
-
- let allZones: OverviewRulerZone[] = [];
- allZones = allZones.concat(this._zonesFromCursors);
- allZones = allZones.concat(this._zonesFromDecorations);
-
- this._overviewRuler.setZones(allZones, false);
+ canvasCtx.fillRect(x[prevLane], prevY1, w[prevLane], prevY2 - prevY1);
}
- let hasRendered = this._overviewRuler.render(false);
+ // Draw cursors
+ if (!this._settings.hideCursor) {
+ const cursorHeight = (2 * this._settings.pixelRatio) | 0;
+ const halfCursorHeight = (cursorHeight / 2) | 0;
+ const x = this._settings.x[OverviewRulerLane.Full];
+ const w = this._settings.w[OverviewRulerLane.Full];
+ canvasCtx.fillStyle = this._settings.cursorColor;
+ for (let i = 0, len = this._cursorPositions.length; i < len; i++) {
+ const cursor = this._cursorPositions[i];
+
+ let yCenter = (viewLayout.getVerticalOffsetForLineNumber(cursor.lineNumber) * heightRatio) | 0;
+ if (yCenter < halfCursorHeight) {
+ yCenter = halfCursorHeight;
+ } else if (yCenter + halfCursorHeight > canvasHeight) {
+ yCenter = canvasHeight - halfCursorHeight;
+ }
+ const y1 = yCenter - halfCursorHeight;
- if (hasRendered && this._renderBorder && this._borderColor && this._overviewRuler.getLanesCount() > 0 && (this._zonesFromDecorations.length > 0 || this._zonesFromCursors.length > 0)) {
- let ctx2 = this._overviewRuler.getDomNode().getContext('2d');
- ctx2.beginPath();
- ctx2.lineWidth = 1;
- ctx2.strokeStyle = this._borderColor;
- ctx2.moveTo(0, 0);
- ctx2.lineTo(0, this._overviewRuler.getPixelHeight());
- ctx2.stroke();
+ canvasCtx.fillRect(x, y1, w, cursorHeight);
+ }
+ }
- ctx2.moveTo(0, 0);
- ctx2.lineTo(this._overviewRuler.getPixelWidth(), 0);
- ctx2.stroke();
+ if (this._settings.renderBorder && this._settings.borderColor && this._settings.overviewRulerLanes > 0) {
+ canvasCtx.beginPath();
+ canvasCtx.lineWidth = 1;
+ canvasCtx.strokeStyle = this._settings.borderColor;
+ canvasCtx.moveTo(0, 0);
+ canvasCtx.lineTo(0, canvasHeight);
+ canvasCtx.stroke();
+
+ canvasCtx.moveTo(0, 0);
+ canvasCtx.lineTo(canvasWidth, 0);
+ canvasCtx.stroke();
}
}
}
+
diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts
index 2c38ec938d230..4881d847e08a8 100644
--- a/src/vs/editor/common/commonCodeEditor.ts
+++ b/src/vs/editor/common/commonCodeEditor.ts
@@ -30,6 +30,7 @@ import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/ed
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
+import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
let EDITOR_ID = 0;
@@ -856,6 +857,26 @@ export abstract class CommonCodeEditor extends Disposable implements editorCommo
this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations);
}
+ public setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void {
+
+ // remove decoration sub types that are no longer used, deregister decoration type if necessary
+ let oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {};
+ for (let subType in oldDecorationsSubTypes) {
+ this._removeDecorationType(decorationTypeKey + '-' + subType);
+ }
+ this._decorationTypeSubtypes[decorationTypeKey] = {};
+
+ const opts = ModelDecorationOptions.createDynamic(this._resolveDecorationOptions(decorationTypeKey, false));
+ let newModelDecorations: editorCommon.IModelDeltaDecoration[] = new Array(ranges.length);
+ for (let i = 0, len = ranges.length; i < len; i++) {
+ newModelDecorations[i] = { range: ranges[i], options: opts };
+ }
+
+ // update all decorations
+ let oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] || [];
+ this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations);
+ }
+
public removeDecorations(decorationTypeKey: string): void {
// remove decorations for type and sub type
let oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey];
diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts
index 65bc4059e5c82..bbe2d1aa6eee3 100644
--- a/src/vs/editor/common/controller/cursor.ts
+++ b/src/vs/editor/common/controller/cursor.ts
@@ -555,8 +555,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
interface IExecContext {
readonly model: editorCommon.IModel;
readonly selectionsBefore: Selection[];
- readonly selectionStartMarkers: string[];
- readonly positionMarkers: string[];
+ readonly trackedRanges: string[];
+ readonly trackedRangesDirection: SelectionDirection[];
}
interface ICommandData {
@@ -576,15 +576,14 @@ class CommandExecutor {
const ctx: IExecContext = {
model: model,
selectionsBefore: selectionsBefore,
- selectionStartMarkers: [],
- positionMarkers: []
+ trackedRanges: [],
+ trackedRangesDirection: []
};
const result = this._innerExecuteCommands(ctx, commands);
- for (let i = 0; i < ctx.selectionStartMarkers.length; i++) {
- ctx.model._removeMarker(ctx.selectionStartMarkers[i]);
- ctx.model._removeMarker(ctx.positionMarkers[i]);
+ for (let i = 0, len = ctx.trackedRanges.length; i < len; i++) {
+ ctx.model._setTrackedRange(ctx.trackedRanges[i], null, editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
}
return result;
@@ -661,9 +660,11 @@ class CommandExecutor {
getTrackedSelection: (id: string) => {
const idx = parseInt(id, 10);
- const selectionStartMarker = ctx.model._getMarker(ctx.selectionStartMarkers[idx]);
- const positionMarker = ctx.model._getMarker(ctx.positionMarkers[idx]);
- return new Selection(selectionStartMarker.lineNumber, selectionStartMarker.column, positionMarker.lineNumber, positionMarker.column);
+ const range = ctx.model._getTrackedRange(ctx.trackedRanges[idx]);
+ if (ctx.trackedRangesDirection[idx] === SelectionDirection.LTR) {
+ return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
+ }
+ return new Selection(range.endLineNumber, range.endColumn, range.startLineNumber, range.startColumn);
}
});
} else {
@@ -750,37 +751,31 @@ class CommandExecutor {
};
const trackSelection = (selection: Selection, trackPreviousOnEmpty?: boolean) => {
- let selectionMarkerStickToPreviousCharacter: boolean;
- let positionMarkerStickToPreviousCharacter: boolean;
-
+ let stickiness: editorCommon.TrackedRangeStickiness;
if (selection.isEmpty()) {
- // Try to lock it with surrounding text
if (typeof trackPreviousOnEmpty === 'boolean') {
- selectionMarkerStickToPreviousCharacter = trackPreviousOnEmpty;
- positionMarkerStickToPreviousCharacter = trackPreviousOnEmpty;
+ if (trackPreviousOnEmpty) {
+ stickiness = editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore;
+ } else {
+ stickiness = editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter;
+ }
} else {
+ // Try to lock it with surrounding text
const maxLineColumn = ctx.model.getLineMaxColumn(selection.startLineNumber);
if (selection.startColumn === maxLineColumn) {
- selectionMarkerStickToPreviousCharacter = true;
- positionMarkerStickToPreviousCharacter = true;
+ stickiness = editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore;
} else {
- selectionMarkerStickToPreviousCharacter = false;
- positionMarkerStickToPreviousCharacter = false;
+ stickiness = editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter;
}
}
} else {
- if (selection.getDirection() === SelectionDirection.LTR) {
- selectionMarkerStickToPreviousCharacter = false;
- positionMarkerStickToPreviousCharacter = true;
- } else {
- selectionMarkerStickToPreviousCharacter = true;
- positionMarkerStickToPreviousCharacter = false;
- }
+ stickiness = editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
}
- const l = ctx.selectionStartMarkers.length;
- ctx.selectionStartMarkers[l] = ctx.model._addMarker(0, selection.selectionStartLineNumber, selection.selectionStartColumn, selectionMarkerStickToPreviousCharacter);
- ctx.positionMarkers[l] = ctx.model._addMarker(0, selection.positionLineNumber, selection.positionColumn, positionMarkerStickToPreviousCharacter);
+ const l = ctx.trackedRanges.length;
+ const id = ctx.model._setTrackedRange(null, selection, stickiness);
+ ctx.trackedRanges[l] = id;
+ ctx.trackedRangesDirection[l] = selection.getDirection();
return l.toString();
};
diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts
index ae9c990ec4a3d..fc2690244fb60 100644
--- a/src/vs/editor/common/controller/oneCursor.ts
+++ b/src/vs/editor/common/controller/oneCursor.ts
@@ -8,16 +8,21 @@ import { SingleCursorState, CursorContext, CursorState } from 'vs/editor/common/
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection, SelectionDirection } from 'vs/editor/common/core/selection';
+import { TrackedRangeStickiness } from 'vs/editor/common/editorCommon';
export class OneCursor {
public modelState: SingleCursorState;
public viewState: SingleCursorState;
- private _selStartMarker: string;
- private _selEndMarker: string;
+ private _selTrackedRange: string;
constructor(context: CursorContext) {
+ this.modelState = null;
+ this.viewState = null;
+
+ this._selTrackedRange = null;
+
this._setState(
context,
new SingleCursorState(new Range(1, 1, 1, 1), 0, new Position(1, 1), 0),
@@ -26,8 +31,7 @@ export class OneCursor {
}
public dispose(context: CursorContext): void {
- context.model._removeMarker(this._selStartMarker);
- context.model._removeMarker(this._selEndMarker);
+ this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, null, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
}
public asCursorState(): CursorState {
@@ -35,14 +39,11 @@ export class OneCursor {
}
public readSelectionFromMarkers(context: CursorContext): Selection {
- const start = context.model._getMarker(this._selStartMarker);
- const end = context.model._getMarker(this._selEndMarker);
-
+ const range = context.model._getTrackedRange(this._selTrackedRange);
if (this.modelState.selection.getDirection() === SelectionDirection.LTR) {
- return new Selection(start.lineNumber, start.column, end.lineNumber, end.column);
+ return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
}
-
- return new Selection(end.lineNumber, end.column, start.lineNumber, start.column);
+ return new Selection(range.endLineNumber, range.endColumn, range.startLineNumber, range.startColumn);
}
public ensureValidState(context: CursorContext): void {
@@ -100,17 +101,6 @@ export class OneCursor {
this.modelState = modelState;
this.viewState = viewState;
- this._selStartMarker = this._ensureMarker(context, this._selStartMarker, this.modelState.selection.startLineNumber, this.modelState.selection.startColumn, true);
- this._selEndMarker = this._ensureMarker(context, this._selEndMarker, this.modelState.selection.endLineNumber, this.modelState.selection.endColumn, false);
- }
-
- private _ensureMarker(context: CursorContext, markerId: string, lineNumber: number, column: number, stickToPreviousCharacter: boolean): string {
- if (!markerId) {
- return context.model._addMarker(0, lineNumber, column, stickToPreviousCharacter);
- } else {
- context.model._changeMarker(markerId, lineNumber, column);
- context.model._changeMarkerStickiness(markerId, stickToPreviousCharacter);
- return markerId;
- }
+ this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, this.modelState.selection, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
}
}
diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts
index ab9db53d0ceb4..a346ca6ea9d50 100644
--- a/src/vs/editor/common/editorCommon.ts
+++ b/src/vs/editor/common/editorCommon.ts
@@ -157,10 +157,6 @@ export interface IModelDecoration {
* Options associated with this decoration.
*/
readonly options: IModelDecorationOptions;
- /**
- * A flag describing if this is a problem decoration (e.g. warning/error).
- */
- readonly isForValidation: boolean;
}
/**
@@ -915,32 +911,6 @@ export interface ITokenizedModel extends ITextModel {
getLineIndentGuide(lineNumber: number): number;
}
-/**
- * A model that can track markers.
- */
-export interface ITextModelWithMarkers extends ITextModel {
- /**
- * @internal
- */
- _addMarker(internalDecorationId: number, lineNumber: number, column: number, stickToPreviousCharacter: boolean): string;
- /**
- * @internal
- */
- _changeMarker(id: string, newLineNumber: number, newColumn: number): void;
- /**
- * @internal
- */
- _changeMarkerStickiness(id: string, newStickToPreviousCharacter: boolean): void;
- /**
- * @internal
- */
- _getMarker(id: string): Position;
- /**
- * @internal
- */
- _removeMarker(id: string): void;
-}
-
/**
* Describes the behavior of decorations when typing/editing near their edges.
* Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior`
@@ -1034,12 +1004,29 @@ export interface ITextModelWithDecorations {
* @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors).
*/
getAllDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[];
+
+ /**
+ * Gets all the decorations that should be rendered in the overview ruler as an array.
+ * @param ownerId If set, it will ignore decorations belonging to other owners.
+ * @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors).
+ */
+ getOverviewRulerDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[];
+
+ /**
+ * @internal
+ */
+ _getTrackedRange(id: string): Range;
+
+ /**
+ * @internal
+ */
+ _setTrackedRange(id: string, newRange: Range, newStickiness: TrackedRangeStickiness): string;
}
/**
* An editable text model.
*/
-export interface IEditableTextModel extends ITextModelWithMarkers {
+export interface IEditableTextModel extends ITextModel {
/**
* Normalize a string containing whitespace according to indentation rules (converts to spaces or to tabs).
@@ -1122,7 +1109,7 @@ export interface IEditableTextModel extends ITextModelWithMarkers {
/**
* A model.
*/
-export interface IModel extends IReadOnlyModel, IEditableTextModel, ITextModelWithMarkers, ITokenizedModel, ITextModelWithDecorations {
+export interface IModel extends IReadOnlyModel, IEditableTextModel, ITokenizedModel, ITextModelWithDecorations {
/**
* @deprecated Please use `onDidChangeContent` instead.
* An event emitted when the contents of the model have changed.
@@ -1977,6 +1964,11 @@ export interface ICommonCodeEditor extends IEditor {
*/
setDecorations(decorationTypeKey: string, ranges: IDecorationOptions[]): void;
+ /**
+ * @internal
+ */
+ setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void;
+
/**
* @internal
*/
diff --git a/src/vs/editor/common/model/editableTextModel.ts b/src/vs/editor/common/model/editableTextModel.ts
index 66566d1092b4f..77c0bd9f8a844 100644
--- a/src/vs/editor/common/model/editableTextModel.ts
+++ b/src/vs/editor/common/model/editableTextModel.ts
@@ -7,12 +7,11 @@
import { Range, IRange } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EditStack } from 'vs/editor/common/model/editStack';
-import { ILineEdit, LineMarker, MarkersTracker, IModelLine } from 'vs/editor/common/model/modelLine';
+import { ILineEdit, IModelLine } from 'vs/editor/common/model/modelLine';
import { TextModelWithDecorations, ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
import * as strings from 'vs/base/common/strings';
import * as arrays from 'vs/base/common/arrays';
import { Selection } from 'vs/editor/common/core/selection';
-import { Position } from 'vs/editor/common/core/position';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource';
@@ -23,6 +22,7 @@ export interface IValidatedEditOperation {
sortIndex: number;
identifier: editorCommon.ISingleEditOperationIdentifier;
range: Range;
+ rangeOffset: number;
rangeLength: number;
lines: string[];
forceMoveMarkers: boolean;
@@ -249,6 +249,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
sortIndex: 0,
identifier: operations[0].identifier,
range: entireEditRange,
+ rangeOffset: this.getOffsetAt(entireEditRange.getStartPosition()),
rangeLength: this.getValueLengthInRange(entireEditRange),
lines: result.join('').split('\n'),
forceMoveMarkers: forceMoveMarkers,
@@ -275,15 +276,15 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] {
try {
this._eventEmitter.beginDeferredEmit();
- let markersTracker = this._acquireMarkersTracker();
- return this._applyEdits(markersTracker, rawOperations);
+ this._acquireDecorationsTracker();
+ return this._applyEdits(rawOperations);
} finally {
- this._releaseMarkersTracker();
+ this._releaseDecorationsTracker();
this._eventEmitter.endDeferredEmit();
}
}
- private _applyEdits(markersTracker: MarkersTracker, rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] {
+ private _applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] {
if (rawOperations.length === 0) {
return [];
}
@@ -310,6 +311,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
sortIndex: i,
identifier: op.identifier,
range: validatedRange,
+ rangeOffset: this.getOffsetAt(validatedRange.getStartPosition()),
rangeLength: this.getValueLengthInRange(validatedRange),
lines: op.text ? op.text.split(/\r\n|\r|\n/) : null,
forceMoveMarkers: op.forceMoveMarkers,
@@ -378,7 +380,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._mightContainRTL = mightContainRTL;
this._mightContainNonBasicASCII = mightContainNonBasicASCII;
- this._doApplyEdits(markersTracker, operations);
+ this._doApplyEdits(operations);
this._trimAutoWhitespaceLines = null;
if (this._options.trimAutoWhitespace && newTrimAutoWhitespaceCandidates.length > 0) {
@@ -465,7 +467,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
return result;
}
- private _doApplyEdits(markersTracker: MarkersTracker, operations: IValidatedEditOperation[]): void {
+ private _doApplyEdits(operations: IValidatedEditOperation[]): void {
const tabSize = this._options.tabSize;
@@ -503,11 +505,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
}
this._invalidateLine(currentLineNumber - 1);
- this._lines[currentLineNumber - 1].applyEdits(markersTracker, lineEditsQueue.slice(currentLineNumberStart, i), tabSize);
- if (this._lineStarts) {
- // update prefix sum
- this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length);
- }
+ this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, i), tabSize);
+ this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text)
);
@@ -517,11 +516,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
}
this._invalidateLine(currentLineNumber - 1);
- this._lines[currentLineNumber - 1].applyEdits(markersTracker, lineEditsQueue.slice(currentLineNumberStart, lineEditsQueue.length), tabSize);
- if (this._lineStarts) {
- // update prefix sum
- this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length);
- }
+ this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, lineEditsQueue.length), tabSize);
+ this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text)
);
@@ -529,10 +525,6 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
lineEditsQueue = [];
};
- let minTouchedLineNumber = operations[operations.length - 1].range.startLineNumber;
- let maxTouchedLineNumber = operations[0].range.endLineNumber + 1;
- let totalLinesCountDelta = 0;
-
for (let i = 0, len = operations.length; i < len; i++) {
const op = operations[i];
@@ -556,8 +548,6 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
const insertingLinesCnt = (op.lines ? op.lines.length - 1 : 0);
const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);
- totalLinesCountDelta += (insertingLinesCnt - deletingLinesCnt);
-
// Iterating descending to overlap with previous op
// in case there are common lines being edited in both
for (let j = editingLinesCnt; j >= 0; j--) {
@@ -567,8 +557,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
lineNumber: editLineNumber,
startColumn: (editLineNumber === startLineNumber ? startColumn : 1),
endColumn: (editLineNumber === endLineNumber ? endColumn : this.getLineMaxColumn(editLineNumber)),
- text: (op.lines ? op.lines[j] : ''),
- forceMoveMarkers: op.forceMoveMarkers
+ text: (op.lines ? op.lines[j] : '')
});
}
@@ -579,43 +568,19 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
flushLineEdits();
const spliceStartLineNumber = startLineNumber + editingLinesCnt;
- const spliceStartColumn = this.getLineMaxColumn(spliceStartLineNumber);
- const endLineRemains = this._lines[endLineNumber - 1].split(markersTracker, endColumn, false, tabSize);
+ const endLineRemains = this._lines[endLineNumber - 1].split(endColumn, tabSize);
this._invalidateLine(spliceStartLineNumber - 1);
const spliceCnt = endLineNumber - spliceStartLineNumber;
- // Collect all these markers
- let markersOnDeletedLines: LineMarker[] = [];
- for (let j = 0; j < spliceCnt; j++) {
- const deleteLineIndex = spliceStartLineNumber + j;
- const deleteLineMarkers = this._lines[deleteLineIndex].getMarkers();
- if (deleteLineMarkers) {
- markersOnDeletedLines = markersOnDeletedLines.concat(deleteLineMarkers);
- }
- }
-
this._lines.splice(spliceStartLineNumber, spliceCnt);
- if (this._lineStarts) {
- // update prefix sum
- this._lineStarts.removeValues(spliceStartLineNumber, spliceCnt);
- }
+ this._lineStarts.removeValues(spliceStartLineNumber, spliceCnt);
// Reconstruct first line
- this._lines[spliceStartLineNumber - 1].append(markersTracker, spliceStartLineNumber, endLineRemains, tabSize);
- if (this._lineStarts) {
- // update prefix sum
- this._lineStarts.changeValue(spliceStartLineNumber - 1, this._lines[spliceStartLineNumber - 1].text.length + this._EOL.length);
- }
+ this._lines[spliceStartLineNumber - 1].append(endLineRemains, tabSize);
+ this._lineStarts.changeValue(spliceStartLineNumber - 1, this._lines[spliceStartLineNumber - 1].text.length + this._EOL.length);
- // Update deleted markers
- const deletedMarkersPosition = new Position(spliceStartLineNumber, spliceStartColumn);
- for (let j = 0, lenJ = markersOnDeletedLines.length; j < lenJ; j++) {
- markersOnDeletedLines[j].updatePosition(markersTracker, deletedMarkersPosition);
- }
-
- this._lines[spliceStartLineNumber - 1].addMarkers(markersOnDeletedLines);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1].text)
);
@@ -638,11 +603,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
}
// Split last line
- let leftoverLine = this._lines[spliceLineNumber - 1].split(markersTracker, spliceColumn, op.forceMoveMarkers, tabSize);
- if (this._lineStarts) {
- // update prefix sum
- this._lineStarts.changeValue(spliceLineNumber - 1, this._lines[spliceLineNumber - 1].text.length + this._EOL.length);
- }
+ let leftoverLine = this._lines[spliceLineNumber - 1].split(spliceColumn, tabSize);
+ this._lineStarts.changeValue(spliceLineNumber - 1, this._lines[spliceLineNumber - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(spliceLineNumber, this._lines[spliceLineNumber - 1].text)
);
@@ -659,44 +621,31 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
}
this._lines = arrays.arrayInsert(this._lines, startLineNumber + editingLinesCnt, newLines);
newLinesContent[newLinesContent.length - 1] += leftoverLine.text;
- if (this._lineStarts) {
- // update prefix sum
- this._lineStarts.insertValues(startLineNumber + editingLinesCnt, newLinesLengths);
- }
+ this._lineStarts.insertValues(startLineNumber + editingLinesCnt, newLinesLengths);
// Last line
- this._lines[startLineNumber + insertingLinesCnt - 1].append(markersTracker, startLineNumber + insertingLinesCnt, leftoverLine, tabSize);
- if (this._lineStarts) {
- // update prefix sum
- this._lineStarts.changeValue(startLineNumber + insertingLinesCnt - 1, this._lines[startLineNumber + insertingLinesCnt - 1].text.length + this._EOL.length);
- }
+ this._lines[startLineNumber + insertingLinesCnt - 1].append(leftoverLine, tabSize);
+ this._lineStarts.changeValue(startLineNumber + insertingLinesCnt - 1, this._lines[startLineNumber + insertingLinesCnt - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLinesContent.join('\n'))
);
}
+ const text = (op.lines ? op.lines.join(this.getEOL()) : '');
contentChanges.push({
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeLength: op.rangeLength,
- text: op.lines ? op.lines.join(this.getEOL()) : ''
+ text: text
});
+ this._adjustDecorationsForEdit(op.rangeOffset, op.rangeLength, text.length, op.forceMoveMarkers);
+
// console.log('AFTER:');
// console.log('<<<\n' + this._lines.map(l => l.text).join('\n') + '\n>>>');
}
flushLineEdits();
- maxTouchedLineNumber = Math.max(1, Math.min(this.getLineCount(), maxTouchedLineNumber + totalLinesCountDelta));
- if (totalLinesCountDelta !== 0) {
- // must update line numbers all the way to the bottom
- maxTouchedLineNumber = this.getLineCount();
- }
-
- for (let lineNumber = minTouchedLineNumber; lineNumber <= maxTouchedLineNumber; lineNumber++) {
- this._lines[lineNumber - 1].updateLineNumber(markersTracker, lineNumber);
- }
-
if (rawContentChanges.length !== 0 || contentChanges.length !== 0) {
this._increaseVersionId();
@@ -718,35 +667,9 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelContentChanged, e);
}
- // this._assertLineNumbersOK();
this._resetIndentRanges();
}
- public _assertLineNumbersOK(): void {
- let foundMarkersCnt = 0;
- for (let i = 0, len = this._lines.length; i < len; i++) {
- let line = this._lines[i];
- let lineNumber = i + 1;
-
- let markers = line.getMarkers();
- if (markers !== null) {
- for (let j = 0, lenJ = markers.length; j < lenJ; j++) {
- foundMarkersCnt++;
- let markerId = markers[j].id;
- let marker = this._markerIdToMarker[markerId];
- if (marker.position.lineNumber !== lineNumber) {
- throw new Error('Misplaced marker with id ' + markerId);
- }
- }
- }
- }
-
- let totalMarkersCnt = Object.keys(this._markerIdToMarker).length;
- if (totalMarkersCnt !== foundMarkersCnt) {
- throw new Error('There are misplaced markers!');
- }
- }
-
private _undo(): Selection[] {
this._isUndoing = true;
let r = this._commandManager.undo();
@@ -764,10 +687,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public undo(): Selection[] {
try {
this._eventEmitter.beginDeferredEmit();
- this._acquireMarkersTracker();
+ this._acquireDecorationsTracker();
return this._undo();
} finally {
- this._releaseMarkersTracker();
+ this._releaseDecorationsTracker();
this._eventEmitter.endDeferredEmit();
}
}
@@ -789,10 +712,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public redo(): Selection[] {
try {
this._eventEmitter.beginDeferredEmit();
- this._acquireMarkersTracker();
+ this._acquireDecorationsTracker();
return this._redo();
} finally {
- this._releaseMarkersTracker();
+ this._releaseDecorationsTracker();
this._eventEmitter.endDeferredEmit();
}
}
diff --git a/src/vs/editor/common/model/intervalTree.ts b/src/vs/editor/common/model/intervalTree.ts
new file mode 100644
index 0000000000000..c07fdd33ba225
--- /dev/null
+++ b/src/vs/editor/common/model/intervalTree.ts
@@ -0,0 +1,1369 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
+import { Range } from 'vs/editor/common/core/range';
+import { IModelDecoration } from 'vs/editor/common/editorCommon';
+
+//
+// The red-black tree is based on the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
+//
+
+export const ClassName = {
+ EditorInfoDecoration: 'infosquiggly',
+ EditorWarningDecoration: 'warningsquiggly',
+ EditorErrorDecoration: 'errorsquiggly'
+};
+
+/**
+ * Describes the behavior of decorations when typing/editing near their edges.
+ * Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior`
+ */
+export const enum TrackedRangeStickiness {
+ AlwaysGrowsWhenTypingAtEdges = 0,
+ NeverGrowsWhenTypingAtEdges = 1,
+ GrowsOnlyWhenTypingBefore = 2,
+ GrowsOnlyWhenTypingAfter = 3,
+}
+
+const enum NodeColor {
+ Black = 0,
+ Red = 1,
+}
+
+const enum Constants {
+ ColorMask = 0b00000001,
+ ColorMaskInverse = 0b11111110,
+ ColorOffset = 0,
+
+ IsVisitedMask = 0b00000010,
+ IsVisitedMaskInverse = 0b11111101,
+ IsVisitedOffset = 1,
+
+ IsForValidationMask = 0b00000100,
+ IsForValidationMaskInverse = 0b11111011,
+ IsForValidationOffset = 2,
+
+ IsInOverviewRulerMask = 0b00001000,
+ IsInOverviewRulerMaskInverse = 0b11110111,
+ IsInOverviewRulerOffset = 3,
+
+ StickinessMask = 0b00110000,
+ StickinessMaskInverse = 0b11001111,
+ StickinessOffset = 4,
+
+ /**
+ * Due to how deletion works (in order to avoid always walking the right subtree of the deleted node),
+ * the deltas for nodes can grow and shrink dramatically. It has been observed, in practice, that unless
+ * the deltas are corrected, integer overflow will occur.
+ *
+ * The integer overflow occurs when 53 bits are used in the numbers, but we will try to avoid it as
+ * a node's delta gets below a negative 30 bits number.
+ *
+ * MIN SMI (SMall Integer) as defined in v8.
+ * one bit is lost for boxing/unboxing flag.
+ * one bit is lost for sign flag.
+ * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
+ */
+ MIN_SAFE_DELTA = -(1 << 30),
+ /**
+ * MAX SMI (SMall Integer) as defined in v8.
+ * one bit is lost for boxing/unboxing flag.
+ * one bit is lost for sign flag.
+ * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
+ */
+ MAX_SAFE_DELTA = 1 << 30,
+}
+
+function getNodeColor(node: IntervalNode): NodeColor {
+ return ((node.metadata & Constants.ColorMask) >>> Constants.ColorOffset);
+}
+function setNodeColor(node: IntervalNode, color: NodeColor): void {
+ node.metadata = (
+ (node.metadata & Constants.ColorMaskInverse) | (color << Constants.ColorOffset)
+ );
+}
+function getNodeIsVisited(node: IntervalNode): boolean {
+ return ((node.metadata & Constants.IsVisitedMask) >>> Constants.IsVisitedOffset) === 1;
+}
+function setNodeIsVisited(node: IntervalNode, value: boolean): void {
+ node.metadata = (
+ (node.metadata & Constants.IsVisitedMaskInverse) | ((value ? 1 : 0) << Constants.IsVisitedOffset)
+ );
+}
+function getNodeIsForValidation(node: IntervalNode): boolean {
+ return ((node.metadata & Constants.IsForValidationMask) >>> Constants.IsForValidationOffset) === 1;
+}
+function setNodeIsForValidation(node: IntervalNode, value: boolean): void {
+ node.metadata = (
+ (node.metadata & Constants.IsForValidationMaskInverse) | ((value ? 1 : 0) << Constants.IsForValidationOffset)
+ );
+}
+export function getNodeIsInOverviewRuler(node: IntervalNode): boolean {
+ return ((node.metadata & Constants.IsInOverviewRulerMask) >>> Constants.IsInOverviewRulerOffset) === 1;
+}
+function setNodeIsInOverviewRuler(node: IntervalNode, value: boolean): void {
+ node.metadata = (
+ (node.metadata & Constants.IsInOverviewRulerMaskInverse) | ((value ? 1 : 0) << Constants.IsInOverviewRulerOffset)
+ );
+}
+function getNodeStickiness(node: IntervalNode): TrackedRangeStickiness {
+ return ((node.metadata & Constants.StickinessMask) >>> Constants.StickinessOffset);
+}
+function setNodeStickiness(node: IntervalNode, stickiness: TrackedRangeStickiness): void {
+ node.metadata = (
+ (node.metadata & Constants.StickinessMaskInverse) | (stickiness << Constants.StickinessOffset)
+ );
+}
+
+export class IntervalNode implements IModelDecoration {
+
+ /**
+ * contains binary encoded information for color, visited, isForValidation and stickiness.
+ */
+ public metadata: number;
+
+ public parent: IntervalNode;
+ public left: IntervalNode;
+ public right: IntervalNode;
+
+ public start: number;
+ public end: number;
+ public delta: number;
+ public maxEnd: number;
+
+ public id: string;
+ public ownerId: number;
+ public options: ModelDecorationOptions;
+
+ public cachedVersionId: number;
+ public cachedAbsoluteStart: number;
+ public cachedAbsoluteEnd: number;
+ public range: Range;
+
+ constructor(id: string, start: number, end: number) {
+ this.metadata = 0;
+
+ this.parent = null;
+ this.left = null;
+ this.right = null;
+ setNodeColor(this, NodeColor.Red);
+
+ this.start = start;
+ this.end = end;
+ // FORCE_OVERFLOWING_TEST: this.delta = start;
+ this.delta = 0;
+ this.maxEnd = end;
+
+ this.id = id;
+ this.ownerId = 0;
+ this.options = null;
+ setNodeIsForValidation(this, false);
+ setNodeStickiness(this, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
+ setNodeIsInOverviewRuler(this, false);
+
+ this.cachedVersionId = 0;
+ this.cachedAbsoluteStart = start;
+ this.cachedAbsoluteEnd = end;
+ this.range = null;
+
+ setNodeIsVisited(this, false);
+ }
+
+ public reset(versionId: number, start: number, end: number, range: Range): void {
+ this.start = start;
+ this.end = end;
+ this.maxEnd = end;
+ this.cachedVersionId = versionId;
+ this.cachedAbsoluteStart = start;
+ this.cachedAbsoluteEnd = end;
+ this.range = range;
+ }
+
+ public setOptions(options: ModelDecorationOptions) {
+ this.options = options;
+ setNodeIsForValidation(this, (
+ this.options.className === ClassName.EditorErrorDecoration
+ || this.options.className === ClassName.EditorWarningDecoration
+ ));
+ setNodeStickiness(this, this.options.stickiness);
+ setNodeIsInOverviewRuler(this, this.options.overviewRuler.color ? true : false);
+ }
+
+ public setCachedOffsets(absoluteStart: number, absoluteEnd: number, cachedVersionId: number): void {
+ if (this.cachedVersionId !== cachedVersionId) {
+ this.range = null;
+ }
+ this.cachedVersionId = cachedVersionId;
+ this.cachedAbsoluteStart = absoluteStart;
+ this.cachedAbsoluteEnd = absoluteEnd;
+ }
+
+ public detach(): void {
+ this.parent = null;
+ this.left = null;
+ this.right = null;
+ }
+}
+
+const SENTINEL: IntervalNode = new IntervalNode(null, 0, 0);
+SENTINEL.parent = SENTINEL;
+SENTINEL.left = SENTINEL;
+SENTINEL.right = SENTINEL;
+setNodeColor(SENTINEL, NodeColor.Black);
+
+export class IntervalTree {
+
+ public root: IntervalNode;
+ public requestNormalizeDelta: boolean;
+
+ constructor() {
+ this.root = SENTINEL;
+ this.requestNormalizeDelta = false;
+ }
+
+ public intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] {
+ if (this.root === SENTINEL) {
+ return [];
+ }
+ return intervalSearch(this, start, end, filterOwnerId, filterOutValidation, cachedVersionId);
+ }
+
+ public search(filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] {
+ if (this.root === SENTINEL) {
+ return [];
+ }
+ return search(this, filterOwnerId, filterOutValidation, cachedVersionId);
+ }
+
+ public count(): number {
+ if (this.root === SENTINEL) {
+ return 0;
+ }
+ return nodeCount(this);
+ }
+
+ /**
+ * Will not set `cachedAbsoluteStart` nor `cachedAbsoluteEnd` on the returned nodes!
+ */
+ public collectNodesFromOwner(ownerId: number): IntervalNode[] {
+ return collectNodesFromOwner(this, ownerId);
+ }
+
+ /**
+ * Will not set `cachedAbsoluteStart` nor `cachedAbsoluteEnd` on the returned nodes!
+ */
+ public collectNodesPostOrder(): IntervalNode[] {
+ return collectNodesPostOrder(this);
+ }
+
+ public insert(node: IntervalNode): void {
+ rbTreeInsert(this, node);
+ this._normalizeDeltaIfNecessary();
+ }
+
+ public delete(node: IntervalNode): void {
+ rbTreeDelete(this, node);
+ this._normalizeDeltaIfNecessary();
+ }
+
+ public resolveNode(node: IntervalNode, cachedVersionId: number): void {
+ const initialNode = node;
+ let delta = 0;
+ while (node !== this.root) {
+ if (node === node.parent.right) {
+ delta += node.parent.delta;
+ }
+ node = node.parent;
+ }
+
+ const nodeStart = initialNode.start + delta;
+ const nodeEnd = initialNode.end + delta;
+ initialNode.setCachedOffsets(nodeStart, nodeEnd, cachedVersionId);
+ }
+
+ public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {
+ // Our strategy is to remove all directly impacted nodes, and then add them back to the tree.
+
+ // (1) collect all nodes that are intersecting this edit as nodes of interest
+ const nodesOfInterest = searchForEditing(this, offset, offset + length);
+
+ // (2) remove all nodes that are intersecting this edit
+ for (let i = 0, len = nodesOfInterest.length; i < len; i++) {
+ const node = nodesOfInterest[i];
+ rbTreeDelete(this, node);
+ }
+ this._normalizeDeltaIfNecessary();
+
+ // (3) edit all tree nodes except the nodes of interest
+ noOverlapReplace(this, offset, offset + length, textLength);
+ this._normalizeDeltaIfNecessary();
+
+ // (4) edit the nodes of interest and insert them back in the tree
+ for (let i = 0, len = nodesOfInterest.length; i < len; i++) {
+ const node = nodesOfInterest[i];
+ node.start = node.cachedAbsoluteStart;
+ node.end = node.cachedAbsoluteEnd;
+ nodeAcceptEdit(node, offset, (offset + length), textLength, forceMoveMarkers);
+ node.maxEnd = node.end;
+ rbTreeInsert(this, node);
+ }
+ this._normalizeDeltaIfNecessary();
+ }
+
+ public assertInvariants(): void {
+ assert(getNodeColor(SENTINEL) === NodeColor.Black);
+ assert(SENTINEL.parent === SENTINEL);
+ assert(SENTINEL.left === SENTINEL);
+ assert(SENTINEL.right === SENTINEL);
+ assert(SENTINEL.start === 0);
+ assert(SENTINEL.end === 0);
+ assert(SENTINEL.delta === 0);
+ assert(this.root.parent === SENTINEL);
+ assertValidTree(this);
+ }
+
+ public getAllInOrder(): IntervalNode[] {
+ return search(this, 0, false, 0);
+ }
+
+ public print(): void {
+ if (this.root === SENTINEL) {
+ console.log(`~~ empty`);
+ return;
+ }
+ let out: string[] = [];
+ this._print(this.root, '', 0, out);
+ console.log(out.join(''));
+ }
+
+ private _print(n: IntervalNode, indent: string, delta: number, out: string[]): void {
+ out.push(`${indent}[${getNodeColor(n) === NodeColor.Red ? 'R' : 'B'},${n.delta}, ${n.start}->${n.end}, ${n.maxEnd}] : {${delta + n.start}->${delta + n.end}}, maxEnd: ${n.maxEnd + delta}\n`);
+ if (n.left !== SENTINEL) {
+ this._print(n.left, indent + ' ', delta, out);
+ } else {
+ out.push(`${indent} NIL\n`);
+ }
+ if (n.right !== SENTINEL) {
+ this._print(n.right, indent + ' ', delta + n.delta, out);
+ } else {
+ out.push(`${indent} NIL\n`);
+ }
+ }
+
+ private _normalizeDeltaIfNecessary(): void {
+ if (!this.requestNormalizeDelta) {
+ return;
+ }
+ this.requestNormalizeDelta = false;
+ normalizeDelta(this);
+ }
+}
+
+//#region Delta Normalization
+function normalizeDelta(T: IntervalTree): void {
+ let node = T.root;
+ let delta = 0;
+ while (node !== SENTINEL) {
+
+ if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {
+ // go left
+ node = node.left;
+ continue;
+ }
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ delta += node.delta;
+ node = node.right;
+ continue;
+ }
+
+ // handle current node
+ node.start = delta + node.start;
+ node.end = delta + node.end;
+ node.delta = 0;
+ recomputeMaxEnd(node);
+
+ setNodeIsVisited(node, true);
+
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ if (node === node.parent.right) {
+ delta -= node.parent.delta;
+ }
+ node = node.parent;
+ }
+
+ setNodeIsVisited(T.root, false);
+}
+//#endregion
+
+//#region Editing
+
+const enum MarkerMoveSemantics {
+ MarkerDefined = 0,
+ ForceMove = 1,
+ ForceStay = 2
+}
+
+function adjustMarkerBeforeColumn(markerOffset: number, markerStickToPreviousCharacter: boolean, checkOffset: number, moveSemantics: MarkerMoveSemantics): boolean {
+ if (markerOffset < checkOffset) {
+ return true;
+ }
+ if (markerOffset > checkOffset) {
+ return false;
+ }
+ if (moveSemantics === MarkerMoveSemantics.ForceMove) {
+ return false;
+ }
+ if (moveSemantics === MarkerMoveSemantics.ForceStay) {
+ return true;
+ }
+ return markerStickToPreviousCharacter;
+};
+
+/**
+ * This is a lot more complicated than strictly necessary to maintain the same behaviour
+ * as when decorations were implemented using two markers.
+ */
+function nodeAcceptEdit(node: IntervalNode, start: number, end: number, textLength: number, forceMoveMarkers: boolean): void {
+ const nodeStickiness = getNodeStickiness(node);
+ const startStickToPreviousCharacter = (
+ nodeStickiness === TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
+ || nodeStickiness === TrackedRangeStickiness.GrowsOnlyWhenTypingBefore
+ );
+ const endStickToPreviousCharacter = (
+ nodeStickiness === TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
+ || nodeStickiness === TrackedRangeStickiness.GrowsOnlyWhenTypingBefore
+ );
+
+ const deletingCnt = (end - start);
+ const insertingCnt = textLength;
+ const commonLength = Math.min(deletingCnt, insertingCnt);
+
+ const nodeStart = node.start;
+ let startDone = false;
+
+ const nodeEnd = node.end;
+ let endDone = false;
+
+ {
+ const moveSemantics = forceMoveMarkers ? MarkerMoveSemantics.ForceMove : (deletingCnt > 0 ? MarkerMoveSemantics.ForceStay : MarkerMoveSemantics.MarkerDefined);
+ if (!startDone && adjustMarkerBeforeColumn(nodeStart, startStickToPreviousCharacter, start, moveSemantics)) {
+ startDone = true;
+ }
+ if (!endDone && adjustMarkerBeforeColumn(nodeEnd, endStickToPreviousCharacter, start, moveSemantics)) {
+ endDone = true;
+ }
+ }
+
+ if (commonLength > 0 && !forceMoveMarkers) {
+ const moveSemantics = (deletingCnt > insertingCnt ? MarkerMoveSemantics.ForceStay : MarkerMoveSemantics.MarkerDefined);
+ if (!startDone && adjustMarkerBeforeColumn(nodeStart, startStickToPreviousCharacter, start + commonLength, moveSemantics)) {
+ startDone = true;
+ }
+ if (!endDone && adjustMarkerBeforeColumn(nodeEnd, endStickToPreviousCharacter, start + commonLength, moveSemantics)) {
+ endDone = true;
+ }
+ }
+
+ {
+ const moveSemantics = forceMoveMarkers ? MarkerMoveSemantics.ForceMove : MarkerMoveSemantics.MarkerDefined;
+ if (!startDone && adjustMarkerBeforeColumn(nodeStart, startStickToPreviousCharacter, end, moveSemantics)) {
+ node.start = start + insertingCnt;
+ startDone = true;
+ }
+ if (!endDone && adjustMarkerBeforeColumn(nodeEnd, endStickToPreviousCharacter, end, moveSemantics)) {
+ node.end = start + insertingCnt;
+ endDone = true;
+ }
+ }
+
+ // Finish
+ const deltaColumn = (insertingCnt - deletingCnt);
+ if (!startDone) {
+ node.start = Math.max(0, nodeStart + deltaColumn);
+ startDone = true;
+ }
+ if (!endDone) {
+ node.end = Math.max(0, nodeEnd + deltaColumn);
+ endDone = true;
+ }
+
+ if (node.start > node.end) {
+ node.end = node.start;
+ }
+}
+
+function searchForEditing(T: IntervalTree, start: number, end: number): IntervalNode[] {
+ // https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree
+ // Now, it is known that two intervals A and B overlap only when both
+ // A.low <= B.high and A.high >= B.low. When searching the trees for
+ // nodes overlapping with a given interval, you can immediately skip:
+ // a) all nodes to the right of nodes whose low value is past the end of the given interval.
+ // b) all nodes that have their maximum 'high' value below the start of the given interval.
+ let node = T.root;
+ let delta = 0;
+ let nodeMaxEnd = 0;
+ let nodeStart = 0;
+ let nodeEnd = 0;
+ let result: IntervalNode[] = [];
+ let resultLen = 0;
+ while (node !== SENTINEL) {
+ if (getNodeIsVisited(node)) {
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ if (node === node.parent.right) {
+ delta -= node.parent.delta;
+ }
+ node = node.parent;
+ continue;
+ }
+
+ if (!getNodeIsVisited(node.left)) {
+ // first time seeing this node
+ nodeMaxEnd = delta + node.maxEnd;
+ if (nodeMaxEnd < start) {
+ // cover case b) from above
+ // there is no need to search this node or its children
+ setNodeIsVisited(node, true);
+ continue;
+ }
+
+ if (node.left !== SENTINEL) {
+ // go left
+ node = node.left;
+ continue;
+ }
+ }
+
+ // handle current node
+ nodeStart = delta + node.start;
+ if (nodeStart > end) {
+ // cover case a) from above
+ // there is no need to search this node or its right subtree
+ setNodeIsVisited(node, true);
+ continue;
+ }
+
+ nodeEnd = delta + node.end;
+ if (nodeEnd >= start) {
+ node.setCachedOffsets(nodeStart, nodeEnd, 0);
+ result[resultLen++] = node;
+ }
+ setNodeIsVisited(node, true);
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ delta += node.delta;
+ node = node.right;
+ continue;
+ }
+ }
+
+ setNodeIsVisited(T.root, false);
+
+ return result;
+}
+
+function noOverlapReplace(T: IntervalTree, start: number, end: number, textLength: number): void {
+ // https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree
+ // Now, it is known that two intervals A and B overlap only when both
+ // A.low <= B.high and A.high >= B.low. When searching the trees for
+ // nodes overlapping with a given interval, you can immediately skip:
+ // a) all nodes to the right of nodes whose low value is past the end of the given interval.
+ // b) all nodes that have their maximum 'high' value below the start of the given interval.
+ let node = T.root;
+ let delta = 0;
+ let nodeMaxEnd = 0;
+ let nodeStart = 0;
+ const editDelta = (textLength - (end - start));
+ while (node !== SENTINEL) {
+ if (getNodeIsVisited(node)) {
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ if (node === node.parent.right) {
+ delta -= node.parent.delta;
+ }
+ recomputeMaxEnd(node);
+ node = node.parent;
+ continue;
+ }
+
+ if (!getNodeIsVisited(node.left)) {
+ // first time seeing this node
+ nodeMaxEnd = delta + node.maxEnd;
+ if (nodeMaxEnd < start) {
+ // cover case b) from above
+ // there is no need to search this node or its children
+ setNodeIsVisited(node, true);
+ continue;
+ }
+
+ if (node.left !== SENTINEL) {
+ // go left
+ node = node.left;
+ continue;
+ }
+ }
+
+ // handle current node
+ nodeStart = delta + node.start;
+ if (nodeStart > end) {
+ node.start += editDelta;
+ node.end += editDelta;
+ node.delta += editDelta;
+ if (node.delta < Constants.MIN_SAFE_DELTA || node.delta > Constants.MAX_SAFE_DELTA) {
+ T.requestNormalizeDelta = true;
+ }
+ // cover case a) from above
+ // there is no need to search this node or its right subtree
+ setNodeIsVisited(node, true);
+ continue;
+ }
+
+ setNodeIsVisited(node, true);
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ delta += node.delta;
+ node = node.right;
+ continue;
+ }
+ }
+
+ setNodeIsVisited(T.root, false);
+}
+
+//#endregion
+
+//#region Searching
+
+function nodeCount(T: IntervalTree): number {
+ let node = T.root;
+ let count = 0;
+ while (node !== SENTINEL) {
+ if (getNodeIsVisited(node)) {
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ node = node.parent;
+ continue;
+ }
+
+ if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {
+ // go left
+ node = node.left;
+ continue;
+ }
+
+ // handle current node
+ count++;
+ setNodeIsVisited(node, true);
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ node = node.right;
+ continue;
+ }
+ }
+
+ setNodeIsVisited(T.root, false);
+
+ return count;
+}
+
+function collectNodesFromOwner(T: IntervalTree, ownerId: number): IntervalNode[] {
+ let node = T.root;
+ let result: IntervalNode[] = [];
+ let resultLen = 0;
+ while (node !== SENTINEL) {
+ if (getNodeIsVisited(node)) {
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ node = node.parent;
+ continue;
+ }
+
+ if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {
+ // go left
+ node = node.left;
+ continue;
+ }
+
+ // handle current node
+ if (node.ownerId === ownerId) {
+ result[resultLen++] = node;
+ }
+
+ setNodeIsVisited(node, true);
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ node = node.right;
+ continue;
+ }
+ }
+
+ setNodeIsVisited(T.root, false);
+
+ return result;
+}
+
+function collectNodesPostOrder(T: IntervalTree): IntervalNode[] {
+ let node = T.root;
+ let result: IntervalNode[] = [];
+ let resultLen = 0;
+ while (node !== SENTINEL) {
+ if (getNodeIsVisited(node)) {
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ node = node.parent;
+ continue;
+ }
+
+ if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {
+ // go left
+ node = node.left;
+ continue;
+ }
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ node = node.right;
+ continue;
+ }
+
+ // handle current node
+ result[resultLen++] = node;
+ setNodeIsVisited(node, true);
+ }
+
+ setNodeIsVisited(T.root, false);
+
+ return result;
+}
+
+function search(T: IntervalTree, filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] {
+ let node = T.root;
+ let delta = 0;
+ let nodeStart = 0;
+ let nodeEnd = 0;
+ let result: IntervalNode[] = [];
+ let resultLen = 0;
+ while (node !== SENTINEL) {
+ if (getNodeIsVisited(node)) {
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ if (node === node.parent.right) {
+ delta -= node.parent.delta;
+ }
+ node = node.parent;
+ continue;
+ }
+
+ if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {
+ // go left
+ node = node.left;
+ continue;
+ }
+
+ // handle current node
+ nodeStart = delta + node.start;
+ nodeEnd = delta + node.end;
+
+ node.setCachedOffsets(nodeStart, nodeEnd, cachedVersionId);
+
+ let include = true;
+ if (filterOwnerId && node.ownerId && node.ownerId !== filterOwnerId) {
+ include = false;
+ }
+ if (filterOutValidation && getNodeIsForValidation(node)) {
+ include = false;
+ }
+ if (include) {
+ result[resultLen++] = node;
+ }
+
+ setNodeIsVisited(node, true);
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ delta += node.delta;
+ node = node.right;
+ continue;
+ }
+ }
+
+ setNodeIsVisited(T.root, false);
+
+ return result;
+}
+
+function intervalSearch(T: IntervalTree, intervalStart: number, intervalEnd: number, filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] {
+ // https://en.wikipedia.org/wiki/Interval_tree#Augmented_tree
+ // Now, it is known that two intervals A and B overlap only when both
+ // A.low <= B.high and A.high >= B.low. When searching the trees for
+ // nodes overlapping with a given interval, you can immediately skip:
+ // a) all nodes to the right of nodes whose low value is past the end of the given interval.
+ // b) all nodes that have their maximum 'high' value below the start of the given interval.
+
+ let node = T.root;
+ let delta = 0;
+ let nodeMaxEnd = 0;
+ let nodeStart = 0;
+ let nodeEnd = 0;
+ let result: IntervalNode[] = [];
+ let resultLen = 0;
+ while (node !== SENTINEL) {
+ if (getNodeIsVisited(node)) {
+ // going up from this node
+ setNodeIsVisited(node.left, false);
+ setNodeIsVisited(node.right, false);
+ if (node === node.parent.right) {
+ delta -= node.parent.delta;
+ }
+ node = node.parent;
+ continue;
+ }
+
+ if (!getNodeIsVisited(node.left)) {
+ // first time seeing this node
+ nodeMaxEnd = delta + node.maxEnd;
+ if (nodeMaxEnd < intervalStart) {
+ // cover case b) from above
+ // there is no need to search this node or its children
+ setNodeIsVisited(node, true);
+ continue;
+ }
+
+ if (node.left !== SENTINEL) {
+ // go left
+ node = node.left;
+ continue;
+ }
+ }
+
+ // handle current node
+ nodeStart = delta + node.start;
+ if (nodeStart > intervalEnd) {
+ // cover case a) from above
+ // there is no need to search this node or its right subtree
+ setNodeIsVisited(node, true);
+ continue;
+ }
+
+ nodeEnd = delta + node.end;
+
+ if (nodeEnd >= intervalStart) {
+ // There is overlap
+ node.setCachedOffsets(nodeStart, nodeEnd, cachedVersionId);
+
+ let include = true;
+ if (filterOwnerId && node.ownerId && node.ownerId !== filterOwnerId) {
+ include = false;
+ }
+ if (filterOutValidation && getNodeIsForValidation(node)) {
+ include = false;
+ }
+
+ if (include) {
+ result[resultLen++] = node;
+ }
+ }
+
+ setNodeIsVisited(node, true);
+
+ if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
+ // go right
+ delta += node.delta;
+ node = node.right;
+ continue;
+ }
+ }
+
+ setNodeIsVisited(T.root, false);
+
+ return result;
+}
+
+//#endregion
+
+//#region Insertion
+function rbTreeInsert(T: IntervalTree, newNode: IntervalNode): IntervalNode {
+ if (T.root === SENTINEL) {
+ newNode.parent = SENTINEL;
+ newNode.left = SENTINEL;
+ newNode.right = SENTINEL;
+ setNodeColor(newNode, NodeColor.Black);
+ T.root = newNode;
+ return T.root;
+ }
+
+ treeInsert(T, newNode);
+
+ recomputeMaxEndWalkToRoot(newNode.parent);
+
+ // repair tree
+ let x = newNode;
+ while (x !== T.root && getNodeColor(x.parent) === NodeColor.Red) {
+ if (x.parent === x.parent.parent.left) {
+ const y = x.parent.parent.right;
+
+ if (getNodeColor(y) === NodeColor.Red) {
+ setNodeColor(x.parent, NodeColor.Black);
+ setNodeColor(y, NodeColor.Black);
+ setNodeColor(x.parent.parent, NodeColor.Red);
+ x = x.parent.parent;
+ } else {
+ if (x === x.parent.right) {
+ x = x.parent;
+ leftRotate(T, x);
+ }
+ setNodeColor(x.parent, NodeColor.Black);
+ setNodeColor(x.parent.parent, NodeColor.Red);
+ rightRotate(T, x.parent.parent);
+ }
+ } else {
+ const y = x.parent.parent.left;
+
+ if (getNodeColor(y) === NodeColor.Red) {
+ setNodeColor(x.parent, NodeColor.Black);
+ setNodeColor(y, NodeColor.Black);
+ setNodeColor(x.parent.parent, NodeColor.Red);
+ x = x.parent.parent;
+ } else {
+ if (x === x.parent.left) {
+ x = x.parent;
+ rightRotate(T, x);
+ }
+ setNodeColor(x.parent, NodeColor.Black);
+ setNodeColor(x.parent.parent, NodeColor.Red);
+ leftRotate(T, x.parent.parent);
+ }
+ }
+ }
+
+ setNodeColor(T.root, NodeColor.Black);
+
+ return newNode;
+}
+
+function treeInsert(T: IntervalTree, z: IntervalNode): void {
+ let delta: number = 0;
+ let x = T.root;
+ const zAbsoluteStart = z.start;
+ const zAbsoluteEnd = z.end;
+ while (true) {
+ const cmp = intervalCompare(zAbsoluteStart, zAbsoluteEnd, x.start + delta, x.end + delta);
+ if (cmp < 0) {
+ // this node should be inserted to the left
+ // => it is not affected by the node's delta
+ if (x.left === SENTINEL) {
+ z.start -= delta;
+ z.end -= delta;
+ z.maxEnd -= delta;
+ x.left = z;
+ break;
+ } else {
+ x = x.left;
+ }
+ } else {
+ // this node should be inserted to the right
+ // => it is not affected by the node's delta
+ if (x.right === SENTINEL) {
+ z.start -= (delta + x.delta);
+ z.end -= (delta + x.delta);
+ z.maxEnd -= (delta + x.delta);
+ x.right = z;
+ break;
+ } else {
+ delta += x.delta;
+ x = x.right;
+ }
+ }
+ }
+
+ z.parent = x;
+ z.left = SENTINEL;
+ z.right = SENTINEL;
+ setNodeColor(z, NodeColor.Red);
+}
+//#endregion
+
+//#region Deletion
+function rbTreeDelete(T: IntervalTree, z: IntervalNode): void {
+
+ let x: IntervalNode;
+ let y: IntervalNode;
+
+ // RB-DELETE except we don't swap z and y in case c)
+ // i.e. we always delete what's pointed at by z.
+
+ if (z.left === SENTINEL) {
+ x = z.right;
+ y = z;
+
+ // x's delta is no longer influenced by z's delta
+ x.delta += z.delta;
+ if (x.delta < Constants.MIN_SAFE_DELTA || x.delta > Constants.MAX_SAFE_DELTA) {
+ T.requestNormalizeDelta = true;
+ }
+ x.start += z.delta;
+ x.end += z.delta;
+
+ } else if (z.right === SENTINEL) {
+ x = z.left;
+ y = z;
+
+ } else {
+ y = leftest(z.right);
+ x = y.right;
+
+ // y's delta is no longer influenced by z's delta,
+ // but we don't want to walk the entire right-hand-side subtree of x.
+ // we therefore maintain z's delta in y, and adjust only x
+ x.start += y.delta;
+ x.end += y.delta;
+ x.delta += y.delta;
+ if (x.delta < Constants.MIN_SAFE_DELTA || x.delta > Constants.MAX_SAFE_DELTA) {
+ T.requestNormalizeDelta = true;
+ }
+
+ y.start += z.delta;
+ y.end += z.delta;
+ y.delta = z.delta;
+ if (y.delta < Constants.MIN_SAFE_DELTA || y.delta > Constants.MAX_SAFE_DELTA) {
+ T.requestNormalizeDelta = true;
+ }
+ }
+
+ if (y === T.root) {
+ T.root = x;
+ setNodeColor(x, NodeColor.Black);
+
+ z.detach();
+ resetSentinel();
+ recomputeMaxEnd(x);
+ T.root.parent = SENTINEL;
+ return;
+ }
+
+ let yWasRed = (getNodeColor(y) === NodeColor.Red);
+
+ if (y === y.parent.left) {
+ y.parent.left = x;
+ } else {
+ y.parent.right = x;
+ }
+
+ if (y === z) {
+ x.parent = y.parent;
+ } else {
+
+ if (y.parent === z) {
+ x.parent = y;
+ } else {
+ x.parent = y.parent;
+ }
+
+ y.left = z.left;
+ y.right = z.right;
+ y.parent = z.parent;
+ setNodeColor(y, getNodeColor(z));
+
+ if (z === T.root) {
+ T.root = y;
+ } else {
+ if (z === z.parent.left) {
+ z.parent.left = y;
+ } else {
+ z.parent.right = y;
+ }
+ }
+
+ if (y.left !== SENTINEL) {
+ y.left.parent = y;
+ }
+ if (y.right !== SENTINEL) {
+ y.right.parent = y;
+ }
+ }
+
+ z.detach();
+
+ if (yWasRed) {
+ recomputeMaxEndWalkToRoot(x.parent);
+ if (y !== z) {
+ recomputeMaxEndWalkToRoot(y);
+ recomputeMaxEndWalkToRoot(y.parent);
+ }
+ resetSentinel();
+ return;
+ }
+
+ recomputeMaxEndWalkToRoot(x);
+ recomputeMaxEndWalkToRoot(x.parent);
+ if (y !== z) {
+ recomputeMaxEndWalkToRoot(y);
+ recomputeMaxEndWalkToRoot(y.parent);
+ }
+
+ // RB-DELETE-FIXUP
+ let w: IntervalNode;
+ while (x !== T.root && getNodeColor(x) === NodeColor.Black) {
+
+ if (x === x.parent.left) {
+ w = x.parent.right;
+
+ if (getNodeColor(w) === NodeColor.Red) {
+ setNodeColor(w, NodeColor.Black);
+ setNodeColor(x.parent, NodeColor.Red);
+ leftRotate(T, x.parent);
+ w = x.parent.right;
+ }
+
+ if (getNodeColor(w.left) === NodeColor.Black && getNodeColor(w.right) === NodeColor.Black) {
+ setNodeColor(w, NodeColor.Red);
+ x = x.parent;
+ } else {
+ if (getNodeColor(w.right) === NodeColor.Black) {
+ setNodeColor(w.left, NodeColor.Black);
+ setNodeColor(w, NodeColor.Red);
+ rightRotate(T, w);
+ w = x.parent.right;
+ }
+
+ setNodeColor(w, getNodeColor(x.parent));
+ setNodeColor(x.parent, NodeColor.Black);
+ setNodeColor(w.right, NodeColor.Black);
+ leftRotate(T, x.parent);
+ x = T.root;
+ }
+
+ } else {
+ w = x.parent.left;
+
+ if (getNodeColor(w) === NodeColor.Red) {
+ setNodeColor(w, NodeColor.Black);
+ setNodeColor(x.parent, NodeColor.Red);
+ rightRotate(T, x.parent);
+ w = x.parent.left;
+ }
+
+ if (getNodeColor(w.left) === NodeColor.Black && getNodeColor(w.right) === NodeColor.Black) {
+ setNodeColor(w, NodeColor.Red);
+ x = x.parent;
+
+ } else {
+ if (getNodeColor(w.left) === NodeColor.Black) {
+ setNodeColor(w.right, NodeColor.Black);
+ setNodeColor(w, NodeColor.Red);
+ leftRotate(T, w);
+ w = x.parent.left;
+ }
+
+ setNodeColor(w, getNodeColor(x.parent));
+ setNodeColor(x.parent, NodeColor.Black);
+ setNodeColor(w.left, NodeColor.Black);
+ rightRotate(T, x.parent);
+ x = T.root;
+ }
+ }
+ }
+
+ setNodeColor(x, NodeColor.Black);
+ resetSentinel();
+}
+
+function leftest(node: IntervalNode): IntervalNode {
+ while (node.left !== SENTINEL) {
+ node = node.left;
+ }
+ return node;
+}
+
+function resetSentinel(): void {
+ SENTINEL.parent = SENTINEL;
+ SENTINEL.delta = 0; // optional
+ SENTINEL.start = 0; // optional
+ SENTINEL.end = 0; // optional
+}
+//#endregion
+
+//#region Rotations
+function leftRotate(T: IntervalTree, x: IntervalNode): void {
+ const y = x.right; // set y.
+
+ y.delta += x.delta; // y's delta is no longer influenced by x's delta
+ if (y.delta < Constants.MIN_SAFE_DELTA || y.delta > Constants.MAX_SAFE_DELTA) {
+ T.requestNormalizeDelta = true;
+ }
+ y.start += x.delta;
+ y.end += x.delta;
+
+ x.right = y.left; // turn y's left subtree into x's right subtree.
+ if (y.left !== SENTINEL) {
+ y.left.parent = x;
+ }
+ y.parent = x.parent; // link x's parent to y.
+ if (x.parent === SENTINEL) {
+ T.root = y;
+ } else if (x === x.parent.left) {
+ x.parent.left = y;
+ } else {
+ x.parent.right = y;
+ }
+
+ y.left = x; // put x on y's left.
+ x.parent = y;
+
+ recomputeMaxEnd(x);
+ recomputeMaxEnd(y);
+}
+
+function rightRotate(T: IntervalTree, y: IntervalNode): void {
+ const x = y.left;
+
+ y.delta -= x.delta;
+ if (y.delta < Constants.MIN_SAFE_DELTA || y.delta > Constants.MAX_SAFE_DELTA) {
+ T.requestNormalizeDelta = true;
+ }
+ y.start -= x.delta;
+ y.end -= x.delta;
+
+ y.left = x.right;
+ if (x.right !== SENTINEL) {
+ x.right.parent = y;
+ }
+ x.parent = y.parent;
+ if (y.parent === SENTINEL) {
+ T.root = x;
+ } else if (y === y.parent.right) {
+ y.parent.right = x;
+ } else {
+ y.parent.left = x;
+ }
+
+ x.right = y;
+ y.parent = x;
+
+ recomputeMaxEnd(y);
+ recomputeMaxEnd(x);
+}
+//#endregion
+
+//#region max end computation
+
+function computeMaxEnd(node: IntervalNode): number {
+ let maxEnd = node.end;
+ if (node.left !== SENTINEL) {
+ const leftMaxEnd = node.left.maxEnd;
+ if (leftMaxEnd > maxEnd) {
+ maxEnd = leftMaxEnd;
+ }
+ }
+ if (node.right !== SENTINEL) {
+ const rightMaxEnd = node.right.maxEnd + node.delta;
+ if (rightMaxEnd > maxEnd) {
+ maxEnd = rightMaxEnd;
+ }
+ }
+ return maxEnd;
+}
+
+export function recomputeMaxEnd(node: IntervalNode): void {
+ node.maxEnd = computeMaxEnd(node);
+}
+
+function recomputeMaxEndWalkToRoot(node: IntervalNode): void {
+ while (node !== SENTINEL) {
+
+ const maxEnd = computeMaxEnd(node);
+
+ if (node.maxEnd === maxEnd) {
+ // no need to go further
+ return;
+ }
+
+ node.maxEnd = maxEnd;
+ node = node.parent;
+ }
+}
+
+//#endregion
+
+//#region utils
+function intervalCompare(aStart: number, aEnd: number, bStart: number, bEnd: number): number {
+ if (aStart === bStart) {
+ return aEnd - bEnd;
+ }
+ return aStart - bStart;
+}
+//#endregion
+
+//#region Assertion
+
+function depth(n: IntervalNode): number {
+ if (n === SENTINEL) {
+ // The leafs are black
+ return 1;
+ }
+ assert(depth(n.left) === depth(n.right));
+ return (getNodeColor(n) === NodeColor.Black ? 1 : 0) + depth(n.left);
+}
+
+function assertValidNode(n: IntervalNode, delta): void {
+ if (n === SENTINEL) {
+ return;
+ }
+
+ let l = n.left;
+ let r = n.right;
+
+ if (getNodeColor(n) === NodeColor.Red) {
+ assert(getNodeColor(l) === NodeColor.Black);
+ assert(getNodeColor(r) === NodeColor.Black);
+ }
+
+ let expectedMaxEnd = n.end;
+ if (l !== SENTINEL) {
+ assert(intervalCompare(l.start + delta, l.end + delta, n.start + delta, n.end + delta) <= 0);
+ expectedMaxEnd = Math.max(expectedMaxEnd, l.maxEnd);
+ }
+ if (r !== SENTINEL) {
+ assert(intervalCompare(n.start + delta, n.end + delta, r.start + delta + n.delta, r.end + delta + n.delta) <= 0);
+ expectedMaxEnd = Math.max(expectedMaxEnd, r.maxEnd + n.delta);
+ }
+ assert(n.maxEnd === expectedMaxEnd);
+
+ assertValidNode(l, delta);
+ assertValidNode(r, delta + n.delta);
+}
+
+function assertValidTree(tree: IntervalTree): void {
+ if (tree.root === SENTINEL) {
+ return;
+ }
+ assert(getNodeColor(tree.root) === NodeColor.Black);
+ assert(depth(tree.root.left) === depth(tree.root.right));
+ assertValidNode(tree.root, 0);
+}
+
+function assert(condition: boolean): void {
+ if (!condition) {
+ throw new Error('Assertion violation');
+ }
+}
+
+//#endregion
diff --git a/src/vs/editor/common/model/model.ts b/src/vs/editor/common/model/model.ts
index 1228592fe0fb5..2e1410fc830bb 100644
--- a/src/vs/editor/common/model/model.ts
+++ b/src/vs/editor/common/model/model.ts
@@ -16,7 +16,7 @@ import { IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
// The hierarchy is:
-// Model -> EditableTextModel -> TextModelWithDecorations -> TextModelWithTrackedRanges -> TextModelWithMarkers -> TextModelWithTokens -> TextModel
+// Model -> EditableTextModel -> TextModelWithDecorations -> TextModelWithTokens -> TextModel
var MODEL_ID = 0;
diff --git a/src/vs/editor/common/model/modelLine.ts b/src/vs/editor/common/model/modelLine.ts
index e71219bd6898d..b811f40a73f65 100644
--- a/src/vs/editor/common/model/modelLine.ts
+++ b/src/vs/editor/common/model/modelLine.ts
@@ -7,94 +7,12 @@
import { IState, FontStyle, StandardTokenType, MetadataConsts, ColorId, LanguageId } from 'vs/editor/common/modes';
import { CharCode } from 'vs/base/common/charCode';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
-import { Position } from 'vs/editor/common/core/position';
import { Constants } from 'vs/editor/common/core/uint';
export interface ILineEdit {
startColumn: number;
endColumn: number;
text: string;
- forceMoveMarkers: boolean;
-}
-
-export class LineMarker {
- _lineMarkerBrand: void;
-
- public readonly id: string;
- public readonly internalDecorationId: number;
-
- public stickToPreviousCharacter: boolean;
- public position: Position;
-
- constructor(id: string, internalDecorationId: number, position: Position, stickToPreviousCharacter: boolean) {
- this.id = id;
- this.internalDecorationId = internalDecorationId;
- this.position = position;
- this.stickToPreviousCharacter = stickToPreviousCharacter;
- }
-
- public toString(): string {
- return '{\'' + this.id + '\';' + this.position.toString() + ',' + this.stickToPreviousCharacter + '}';
- }
-
- public updateLineNumber(markersTracker: MarkersTracker, lineNumber: number): void {
- if (this.position.lineNumber === lineNumber) {
- return;
- }
- markersTracker.addChangedMarker(this);
- this.position = new Position(lineNumber, this.position.column);
- }
-
- public updateColumn(markersTracker: MarkersTracker, column: number): void {
- if (this.position.column === column) {
- return;
- }
- markersTracker.addChangedMarker(this);
- this.position = new Position(this.position.lineNumber, column);
- }
-
- public updatePosition(markersTracker: MarkersTracker, position: Position): void {
- if (this.position.lineNumber === position.lineNumber && this.position.column === position.column) {
- return;
- }
- markersTracker.addChangedMarker(this);
- this.position = position;
- }
-
- public setPosition(position: Position) {
- this.position = position;
- }
-
-
- public static compareMarkers(a: LineMarker, b: LineMarker): number {
- if (a.position.column === b.position.column) {
- return (a.stickToPreviousCharacter ? 0 : 1) - (b.stickToPreviousCharacter ? 0 : 1);
- }
- return a.position.column - b.position.column;
- }
-}
-
-export class MarkersTracker {
- _changedDecorationsBrand: void;
-
- private _changedDecorations: number[];
- private _changedDecorationsLen: number;
-
- constructor() {
- this._changedDecorations = [];
- this._changedDecorationsLen = 0;
- }
-
- public addChangedMarker(marker: LineMarker): void {
- let internalDecorationId = marker.internalDecorationId;
- if (internalDecorationId !== 0) {
- this._changedDecorations[this._changedDecorationsLen++] = internalDecorationId;
- }
- }
-
- public getDecorationIds(): number[] {
- return this._changedDecorations;
- }
}
export interface ITokensAdjuster {
@@ -102,27 +20,10 @@ export interface ITokensAdjuster {
finish(delta: number, lineTextLength: number): void;
}
-interface IMarkersAdjuster {
- adjustDelta(toColumn: number, delta: number, minimumAllowedColumn: number, moveSemantics: MarkerMoveSemantics): void;
- adjustSet(toColumn: number, newColumn: number, moveSemantics: MarkerMoveSemantics): void;
- finish(delta: number, lineTextLength: number): void;
-}
-
var NO_OP_TOKENS_ADJUSTER: ITokensAdjuster = {
adjust: () => { },
finish: () => { }
};
-var NO_OP_MARKERS_ADJUSTER: IMarkersAdjuster = {
- adjustDelta: () => { },
- adjustSet: () => { },
- finish: () => { }
-};
-
-const enum MarkerMoveSemantics {
- MarkerDefined = 0,
- ForceMove = 1,
- ForceStay = 2
-}
/**
* Returns:
@@ -156,13 +57,6 @@ function computePlusOneIndentLevel(line: string, tabSize: number): number {
export interface IModelLine {
readonly text: string;
- // --- markers
- addMarker(marker: LineMarker): void;
- addMarkers(markers: LineMarker[]): void;
- removeMarker(marker: LineMarker): void;
- removeMarkers(deleteMarkers: { [markerId: string]: boolean; }): void;
- getMarkers(): LineMarker[];
-
// --- tokenization
resetTokenizationState(): void;
isInvalid(): boolean;
@@ -177,20 +71,14 @@ export interface IModelLine {
getIndentLevel(): number;
// --- editing
- updateLineNumber(markersTracker: MarkersTracker, newLineNumber: number): void;
- applyEdits(markersTracker: MarkersTracker, edits: ILineEdit[], tabSize: number): number;
- append(markersTracker: MarkersTracker, myLineNumber: number, other: IModelLine, tabSize: number): void;
- split(markersTracker: MarkersTracker, splitColumn: number, forceMoveMarkers: boolean, tabSize: number): IModelLine;
+ applyEdits(edits: ILineEdit[], tabSize: number): number;
+ append(other: IModelLine, tabSize: number): void;
+ split(splitColumn: number, tabSize: number): IModelLine;
}
export abstract class AbstractModelLine {
- private _markers: LineMarker[];
-
- constructor(initializeMarkers: boolean) {
- if (initializeMarkers) {
- this._markers = null;
- }
+ constructor() {
}
///
@@ -202,121 +90,18 @@ export abstract class AbstractModelLine {
///
- // private _printMarkers(): string {
- // if (!this._markers) {
- // return '[]';
- // }
- // if (this._markers.length === 0) {
- // return '[]';
- // }
-
- // var markers = this._markers;
-
- // var printMarker = (m:LineMarker) => {
- // if (m.stickToPreviousCharacter) {
- // return '|' + m.position.column;
- // }
- // return m.position.column + '|';
- // };
- // return '[' + markers.map(printMarker).join(', ') + ']';
- // }
-
- private _createMarkersAdjuster(markersTracker: MarkersTracker): IMarkersAdjuster {
- if (!this._markers) {
- return NO_OP_MARKERS_ADJUSTER;
- }
- if (this._markers.length === 0) {
- return NO_OP_MARKERS_ADJUSTER;
- }
-
- this._markers.sort(LineMarker.compareMarkers);
-
- var markers = this._markers;
- var markersLength = markers.length;
- var markersIndex = 0;
- var marker = markers[markersIndex];
-
- // console.log('------------- INITIAL MARKERS: ' + this._printMarkers());
-
- let adjustMarkerBeforeColumn = (toColumn: number, moveSemantics: MarkerMoveSemantics) => {
- if (marker.position.column < toColumn) {
- return true;
- }
- if (marker.position.column > toColumn) {
- return false;
- }
- if (moveSemantics === MarkerMoveSemantics.ForceMove) {
- return false;
- }
- if (moveSemantics === MarkerMoveSemantics.ForceStay) {
- return true;
- }
- return marker.stickToPreviousCharacter;
- };
-
- let adjustDelta = (toColumn: number, delta: number, minimumAllowedColumn: number, moveSemantics: MarkerMoveSemantics) => {
- // console.log('------------------------------');
- // console.log('adjustDelta called: toColumn: ' + toColumn + ', delta: ' + delta + ', minimumAllowedColumn: ' + minimumAllowedColumn + ', moveSemantics: ' + MarkerMoveSemantics[moveSemantics]);
- // console.log('BEFORE::: markersIndex: ' + markersIndex + ' : ' + this._printMarkers());
-
- while (markersIndex < markersLength && adjustMarkerBeforeColumn(toColumn, moveSemantics)) {
- if (delta !== 0) {
- let newColumn = Math.max(minimumAllowedColumn, marker.position.column + delta);
- marker.updateColumn(markersTracker, newColumn);
- }
-
- markersIndex++;
- if (markersIndex < markersLength) {
- marker = markers[markersIndex];
- }
- }
-
- // console.log('AFTER::: markersIndex: ' + markersIndex + ' : ' + this._printMarkers());
- };
-
- let adjustSet = (toColumn: number, newColumn: number, moveSemantics: MarkerMoveSemantics) => {
- // console.log('------------------------------');
- // console.log('adjustSet called: toColumn: ' + toColumn + ', newColumn: ' + newColumn + ', moveSemantics: ' + MarkerMoveSemantics[moveSemantics]);
- // console.log('BEFORE::: markersIndex: ' + markersIndex + ' : ' + this._printMarkers());
-
- while (markersIndex < markersLength && adjustMarkerBeforeColumn(toColumn, moveSemantics)) {
- marker.updateColumn(markersTracker, newColumn);
-
- markersIndex++;
- if (markersIndex < markersLength) {
- marker = markers[markersIndex];
- }
- }
-
- // console.log('AFTER::: markersIndex: ' + markersIndex + ' : ' + this._printMarkers());
- };
-
- let finish = (delta: number, lineTextLength: number) => {
- adjustDelta(Constants.MAX_SAFE_SMALL_INTEGER, delta, 1, MarkerMoveSemantics.MarkerDefined);
-
- // console.log('------------- FINAL MARKERS: ' + this._printMarkers());
- };
-
- return {
- adjustDelta: adjustDelta,
- adjustSet: adjustSet,
- finish: finish
- };
- }
-
- public applyEdits(markersTracker: MarkersTracker, edits: ILineEdit[], tabSize: number): number {
+ public applyEdits(edits: ILineEdit[], tabSize: number): number {
let deltaColumn = 0;
let resultText = this.text;
let tokensAdjuster = this._createTokensAdjuster();
- let markersAdjuster = this._createMarkersAdjuster(markersTracker);
for (let i = 0, len = edits.length; i < len; i++) {
let edit = edits[i];
// console.log();
// console.log('=============================');
- // console.log('EDIT #' + i + ' [ ' + edit.startColumn + ' -> ' + edit.endColumn + ' ] : <<<' + edit.text + '>>>, forceMoveMarkers: ' + edit.forceMoveMarkers);
+ // console.log('EDIT #' + i + ' [ ' + edit.startColumn + ' -> ' + edit.endColumn + ' ] : <<<' + edit.text + '>>>');
// console.log('deltaColumn: ' + deltaColumn);
let startColumn = deltaColumn + edit.startColumn;
@@ -324,35 +109,28 @@ export abstract class AbstractModelLine {
let deletingCnt = endColumn - startColumn;
let insertingCnt = edit.text.length;
- // Adjust tokens & markers before this edit
- // console.log('Adjust tokens & markers before this edit');
+ // Adjust tokens before this edit
+ // console.log('Adjust tokens before this edit');
tokensAdjuster.adjust(edit.startColumn - 1, deltaColumn, 1);
- markersAdjuster.adjustDelta(edit.startColumn, deltaColumn, 1, edit.forceMoveMarkers ? MarkerMoveSemantics.ForceMove : (deletingCnt > 0 ? MarkerMoveSemantics.ForceStay : MarkerMoveSemantics.MarkerDefined));
- // Adjust tokens & markers for the common part of this edit
+ // Adjust tokens for the common part of this edit
let commonLength = Math.min(deletingCnt, insertingCnt);
if (commonLength > 0) {
- // console.log('Adjust tokens & markers for the common part of this edit');
+ // console.log('Adjust tokens for the common part of this edit');
tokensAdjuster.adjust(edit.startColumn - 1 + commonLength, deltaColumn, startColumn);
-
- if (!edit.forceMoveMarkers) {
- markersAdjuster.adjustDelta(edit.startColumn + commonLength, deltaColumn, startColumn, edit.forceMoveMarkers ? MarkerMoveSemantics.ForceMove : (deletingCnt > insertingCnt ? MarkerMoveSemantics.ForceStay : MarkerMoveSemantics.MarkerDefined));
- }
}
// Perform the edit & update `deltaColumn`
resultText = resultText.substring(0, startColumn - 1) + edit.text + resultText.substring(endColumn - 1);
deltaColumn += insertingCnt - deletingCnt;
- // Adjust tokens & markers inside this edit
- // console.log('Adjust tokens & markers inside this edit');
+ // Adjust tokens inside this edit
+ // console.log('Adjust tokens inside this edit');
tokensAdjuster.adjust(edit.endColumn, deltaColumn, startColumn);
- markersAdjuster.adjustSet(edit.endColumn, startColumn + insertingCnt, edit.forceMoveMarkers ? MarkerMoveSemantics.ForceMove : MarkerMoveSemantics.MarkerDefined);
}
- // Wrap up tokens & markers; adjust remaining if needed
+ // Wrap up tokens; adjust remaining if needed
tokensAdjuster.finish(deltaColumn, resultText.length);
- markersAdjuster.finish(deltaColumn, resultText.length);
// Save the resulting text
this._setText(resultText, tabSize);
@@ -360,157 +138,16 @@ export abstract class AbstractModelLine {
return deltaColumn;
}
- public split(markersTracker: MarkersTracker, splitColumn: number, forceMoveMarkers: boolean, tabSize: number): IModelLine {
- // console.log('--> split @ ' + splitColumn + '::: ' + this._printMarkers());
- var myText = this.text.substring(0, splitColumn - 1);
- var otherText = this.text.substring(splitColumn - 1);
-
- var otherMarkers: LineMarker[] = null;
-
- if (this._markers) {
- this._markers.sort(LineMarker.compareMarkers);
- for (let i = 0, len = this._markers.length; i < len; i++) {
- let marker = this._markers[i];
-
- if (
- marker.position.column > splitColumn
- || (
- marker.position.column === splitColumn
- && (
- forceMoveMarkers
- || !marker.stickToPreviousCharacter
- )
- )
- ) {
- let myMarkers = this._markers.slice(0, i);
- otherMarkers = this._markers.slice(i);
- this._markers = myMarkers;
- break;
- }
- }
-
- if (otherMarkers) {
- for (let i = 0, len = otherMarkers.length; i < len; i++) {
- let marker = otherMarkers[i];
-
- marker.updateColumn(markersTracker, marker.position.column - (splitColumn - 1));
- }
- }
- }
+ public split(splitColumn: number, tabSize: number): IModelLine {
+ const myText = this.text.substring(0, splitColumn - 1);
+ const otherText = this.text.substring(splitColumn - 1);
this._setText(myText, tabSize);
-
- var otherLine = this._createModelLine(otherText, tabSize);
- if (otherMarkers) {
- otherLine.addMarkers(otherMarkers);
- }
- return otherLine;
+ return this._createModelLine(otherText, tabSize);
}
- public append(markersTracker: MarkersTracker, myLineNumber: number, other: IModelLine, tabSize: number): void {
- // console.log('--> append: THIS :: ' + this._printMarkers());
- // console.log('--> append: OTHER :: ' + this._printMarkers());
- let thisTextLength = this.text.length;
+ public append(other: IModelLine, tabSize: number): void {
this._setText(this.text + other.text, tabSize);
-
- if (other instanceof AbstractModelLine) {
- if (other._markers) {
- // Other has markers
- let otherMarkers = other._markers;
-
- // Adjust other markers
- for (let i = 0, len = otherMarkers.length; i < len; i++) {
- let marker = otherMarkers[i];
-
- marker.updatePosition(markersTracker, new Position(myLineNumber, marker.position.column + thisTextLength));
- }
-
- this.addMarkers(otherMarkers);
- }
- }
- }
-
- public addMarker(marker: LineMarker): void {
- if (!this._markers) {
- this._markers = [marker];
- } else {
- this._markers.push(marker);
- }
- }
-
- public addMarkers(markers: LineMarker[]): void {
- if (markers.length === 0) {
- return;
- }
-
- if (!this._markers) {
- this._markers = markers.slice(0);
- } else {
- this._markers = this._markers.concat(markers);
- }
- }
-
- public removeMarker(marker: LineMarker): void {
- if (!this._markers) {
- return;
- }
-
- let index = this._indexOfMarkerId(marker.id);
- if (index < 0) {
- return;
- }
-
- if (this._markers.length === 1) {
- // was last marker on line
- this._markers = null;
- } else {
- this._markers.splice(index, 1);
- }
- }
-
- public removeMarkers(deleteMarkers: { [markerId: string]: boolean; }): void {
- if (!this._markers) {
- return;
- }
- for (let i = 0, len = this._markers.length; i < len; i++) {
- let marker = this._markers[i];
-
- if (deleteMarkers[marker.id]) {
- this._markers.splice(i, 1);
- len--;
- i--;
- }
- }
- if (this._markers.length === 0) {
- this._markers = null;
- }
- }
-
- public getMarkers(): LineMarker[] {
- if (!this._markers) {
- return null;
- }
- return this._markers;
- }
-
- public updateLineNumber(markersTracker: MarkersTracker, newLineNumber: number): void {
- if (this._markers) {
- let markers = this._markers;
- for (let i = 0, len = markers.length; i < len; i++) {
- let marker = markers[i];
- marker.updateLineNumber(markersTracker, newLineNumber);
- }
- }
- }
-
- private _indexOfMarkerId(markerId: string): number {
- let markers = this._markers;
- for (let i = 0, len = markers.length; i < len; i++) {
- if (markers[i].id === markerId) {
- return i;
- }
- }
- return undefined;
}
}
@@ -559,7 +196,7 @@ export class ModelLine extends AbstractModelLine implements IModelLine {
private _lineTokens: ArrayBuffer;
constructor(text: string, tabSize: number) {
- super(true);
+ super();
this._metadata = 0;
this._setText(text, tabSize);
this._state = null;
@@ -570,8 +207,8 @@ export class ModelLine extends AbstractModelLine implements IModelLine {
return new ModelLine(text, tabSize);
}
- public split(markersTracker: MarkersTracker, splitColumn: number, forceMoveMarkers: boolean, tabSize: number): IModelLine {
- let result = super.split(markersTracker, splitColumn, forceMoveMarkers, tabSize);
+ public split(splitColumn: number, tabSize: number): IModelLine {
+ let result = super.split(splitColumn, tabSize);
// Mark overflowing tokens for deletion & delete marked tokens
this._deleteMarkedTokens(this._markOverflowingTokensForDeletion(0, this.text.length));
@@ -579,10 +216,10 @@ export class ModelLine extends AbstractModelLine implements IModelLine {
return result;
}
- public append(markersTracker: MarkersTracker, myLineNumber: number, other: IModelLine, tabSize: number): void {
+ public append(other: IModelLine, tabSize: number): void {
let thisTextLength = this.text.length;
- super.append(markersTracker, myLineNumber, other, tabSize);
+ super.append(other, tabSize);
if (other instanceof ModelLine) {
let otherRawTokens = other._lineTokens;
@@ -828,7 +465,7 @@ export class MinimalModelLine extends AbstractModelLine implements IModelLine {
}
constructor(text: string, tabSize: number) {
- super(false);
+ super();
this._setText(text, tabSize);
}
@@ -836,12 +473,12 @@ export class MinimalModelLine extends AbstractModelLine implements IModelLine {
return new MinimalModelLine(text, tabSize);
}
- public split(markersTracker: MarkersTracker, splitColumn: number, forceMoveMarkers: boolean, tabSize: number): IModelLine {
- return super.split(markersTracker, splitColumn, forceMoveMarkers, tabSize);
+ public split(splitColumn: number, tabSize: number): IModelLine {
+ return super.split(splitColumn, tabSize);
}
- public append(markersTracker: MarkersTracker, myLineNumber: number, other: IModelLine, tabSize: number): void {
- super.append(markersTracker, myLineNumber, other, tabSize);
+ public append(other: IModelLine, tabSize: number): void {
+ super.append(other, tabSize);
}
// --- BEGIN STATE
diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts
index 3e55882ed9605..aa93b0ed106e7 100644
--- a/src/vs/editor/common/model/textModel.ts
+++ b/src/vs/editor/common/model/textModel.ts
@@ -8,6 +8,7 @@ import { OrderGuaranteeEventEmitter, BulkListenerCallback } from 'vs/base/common
import * as strings from 'vs/base/common/strings';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { Range, IRange } from 'vs/editor/common/core/range';
+import { Selection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ModelLine, IModelLine, MinimalModelLine } from 'vs/editor/common/model/modelLine';
import { guessIndentation } from 'vs/editor/common/model/indentationGuesser';
@@ -18,8 +19,6 @@ import { TextSource, ITextSource, IRawTextSource, RawTextSource } from 'vs/edito
import { IDisposable } from 'vs/base/common/lifecycle';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
-const USE_MIMINAL_MODEL_LINE = true;
-
const LIMIT_FIND_COUNT = 999;
export const LONG_LINE_BOUNDARY = 10000;
@@ -123,7 +122,7 @@ export class TextModel implements editorCommon.ITextModel {
}
protected _createModelLine(text: string, tabSize: number): IModelLine {
- if (USE_MIMINAL_MODEL_LINE && this._isTooLargeForTokenization) {
+ if (this._isTooLargeForTokenization) {
return new MinimalModelLine(text, tabSize);
}
return new ModelLine(text, tabSize);
@@ -261,22 +260,9 @@ export class TextModel implements editorCommon.ITextModel {
return this._alternativeVersionId;
}
- private _ensureLineStarts(): void {
- if (!this._lineStarts) {
- const eolLength = this._EOL.length;
- const linesLength = this._lines.length;
- const lineStartValues = new Uint32Array(linesLength);
- for (let i = 0; i < linesLength; i++) {
- lineStartValues[i] = this._lines[i].text.length + eolLength;
- }
- this._lineStarts = new PrefixSumComputer(lineStartValues);
- }
- }
-
public getOffsetAt(rawPosition: IPosition): number {
this._assertNotDisposed();
let position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, false);
- this._ensureLineStarts();
return this._lineStarts.getAccumulatedValue(position.lineNumber - 2) + position.column - 1;
}
@@ -285,7 +271,6 @@ export class TextModel implements editorCommon.ITextModel {
offset = Math.floor(offset);
offset = Math.max(0, offset);
- this._ensureLineStarts();
let out = this._lineStarts.getIndexOf(offset);
let lineLength = this._lines[out.index].text.length;
@@ -525,6 +510,12 @@ export class TextModel implements editorCommon.ITextModel {
return this._EOL;
}
+ protected _onBeforeEOLChange(): void {
+ }
+
+ protected _onAfterEOLChange(): void {
+ }
+
public setEOL(eol: editorCommon.EndOfLineSequence): void {
this._assertNotDisposed();
const newEOL = (eol === editorCommon.EndOfLineSequence.CRLF ? '\r\n' : '\n');
@@ -538,9 +529,11 @@ export class TextModel implements editorCommon.ITextModel {
const endLineNumber = this.getLineCount();
const endColumn = this.getLineMaxColumn(endLineNumber);
+ this._onBeforeEOLChange();
this._EOL = newEOL;
- this._lineStarts = null;
+ this._constructLineStarts();
this._increaseVersionId();
+ this._onAfterEOLChange();
this._emitModelRawContentChangedEvent(
new textModelEvents.ModelRawContentChangedEvent(
@@ -607,6 +600,77 @@ export class TextModel implements editorCommon.ITextModel {
return lineNumber;
}
+ /**
+ * Validates `range` is within buffer bounds, but allows it to sit in between surrogate pairs, etc.
+ * Will try to not allocate if possible.
+ */
+ protected _validateRangeRelaxedNoAllocations(range: IRange): Range {
+ const linesCount = this._lines.length;
+
+ const initialStartLineNumber = range.startLineNumber;
+ const initialStartColumn = range.startColumn;
+ let startLineNumber: number;
+ let startColumn: number;
+
+ if (initialStartLineNumber < 1) {
+ startLineNumber = 1;
+ startColumn = 1;
+ } else if (initialStartLineNumber > linesCount) {
+ startLineNumber = linesCount;
+ startColumn = this.getLineMaxColumn(startLineNumber);
+ } else {
+ startLineNumber = initialStartLineNumber | 0;
+ if (initialStartColumn <= 1) {
+ startColumn = 1;
+ } else {
+ const maxColumn = this.getLineMaxColumn(startLineNumber);
+ if (initialStartColumn >= maxColumn) {
+ startColumn = maxColumn;
+ } else {
+ startColumn = initialStartColumn | 0;
+ }
+ }
+ }
+
+ const initialEndLineNumber = range.endLineNumber;
+ const initialEndColumn = range.endColumn;
+ let endLineNumber: number;
+ let endColumn: number;
+
+ if (initialEndLineNumber < 1) {
+ endLineNumber = 1;
+ endColumn = 1;
+ } else if (initialEndLineNumber > linesCount) {
+ endLineNumber = linesCount;
+ endColumn = this.getLineMaxColumn(endLineNumber);
+ } else {
+ endLineNumber = initialEndLineNumber | 0;
+ if (initialEndColumn <= 1) {
+ endColumn = 1;
+ } else {
+ const maxColumn = this.getLineMaxColumn(endLineNumber);
+ if (initialEndColumn >= maxColumn) {
+ endColumn = maxColumn;
+ } else {
+ endColumn = initialEndColumn | 0;
+ }
+ }
+ }
+
+ if (
+ initialStartLineNumber === startLineNumber
+ && initialStartColumn === startColumn
+ && initialEndLineNumber === endLineNumber
+ && initialEndColumn === endColumn
+ && range instanceof Range
+ && !(range instanceof Selection)
+ ) {
+ return range;
+ }
+
+ return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
+ }
+
/**
* @param strict Do NOT allow a position inside a high-low surrogate pair
*/
@@ -723,7 +787,17 @@ export class TextModel implements editorCommon.ITextModel {
this._mightContainNonBasicASCII = !textSource.isBasicASCII;
this._EOL = textSource.EOL;
this._lines = modelLines;
- this._lineStarts = null;
+ this._constructLineStarts();
+ }
+
+ private _constructLineStarts(): void {
+ const eolLength = this._EOL.length;
+ const linesLength = this._lines.length;
+ const lineStartValues = new Uint32Array(linesLength);
+ for (let i = 0; i < linesLength; i++) {
+ lineStartValues[i] = this._lines[i].text.length + eolLength;
+ }
+ this._lineStarts = new PrefixSumComputer(lineStartValues);
}
private _getEndOfLine(eol: editorCommon.EndOfLinePreference): string {
diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts
index 2acaf260be289..aaa66c7beabf5 100644
--- a/src/vs/editor/common/model/textModelEvents.ts
+++ b/src/vs/editor/common/model/textModelEvents.ts
@@ -88,18 +88,6 @@ export interface IModelContentChangedEvent {
* An event describing that model decorations have changed.
*/
export interface IModelDecorationsChangedEvent {
- /**
- * Lists of ids for added decorations.
- */
- readonly addedDecorations: string[];
- /**
- * Lists of ids for changed decorations.
- */
- readonly changedDecorations: string[];
- /**
- * List of ids for removed decorations.
- */
- readonly removedDecorations: string[];
}
/**
diff --git a/src/vs/editor/common/model/textModelWithDecorations.ts b/src/vs/editor/common/model/textModelWithDecorations.ts
index d86288a6b2cf9..ff79fbfccce32 100644
--- a/src/vs/editor/common/model/textModelWithDecorations.ts
+++ b/src/vs/editor/common/model/textModelWithDecorations.ts
@@ -10,107 +10,12 @@ import * as strings from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import { Range, IRange } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
-import { MarkersTracker, LineMarker } from 'vs/editor/common/model/modelLine';
-import { Position } from 'vs/editor/common/core/position';
-import { INewMarker, TextModelWithMarkers } from 'vs/editor/common/model/textModelWithMarkers';
+import { TextModelWithTokens } from 'vs/editor/common/model/textModelWithTokens';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
-
-export const ClassName = {
- EditorInfoDecoration: 'infosquiggly',
- EditorWarningDecoration: 'warningsquiggly',
- EditorErrorDecoration: 'errorsquiggly'
-};
-
-class DecorationsTracker {
-
- public addedDecorations: string[];
- public addedDecorationsLen: number;
- public changedDecorations: string[];
- public changedDecorationsLen: number;
- public removedDecorations: string[];
- public removedDecorationsLen: number;
-
- constructor() {
- this.addedDecorations = [];
- this.addedDecorationsLen = 0;
- this.changedDecorations = [];
- this.changedDecorationsLen = 0;
- this.removedDecorations = [];
- this.removedDecorationsLen = 0;
- }
-
- // --- Build decoration events
-
- public addNewDecoration(id: string): void {
- this.addedDecorations[this.addedDecorationsLen++] = id;
- }
-
- public addRemovedDecoration(id: string): void {
- this.removedDecorations[this.removedDecorationsLen++] = id;
- }
-
- public addMovedDecoration(id: string): void {
- this.changedDecorations[this.changedDecorationsLen++] = id;
- }
-
- public addUpdatedDecoration(id: string): void {
- this.changedDecorations[this.changedDecorationsLen++] = id;
- }
-}
-
-export class InternalDecoration implements editorCommon.IModelDecoration {
- _internalDecorationBrand: void;
-
- public readonly id: string;
- public readonly internalId: number;
- public readonly ownerId: number;
- public readonly startMarker: LineMarker;
- public readonly endMarker: LineMarker;
- public options: ModelDecorationOptions;
- public isForValidation: boolean;
- public range: Range;
-
- constructor(id: string, internalId: number, ownerId: number, range: Range, startMarker: LineMarker, endMarker: LineMarker, options: ModelDecorationOptions) {
- this.id = id;
- this.internalId = internalId;
- this.ownerId = ownerId;
- this.range = range;
- this.startMarker = startMarker;
- this.endMarker = endMarker;
- this.setOptions(options);
- }
-
- public setOptions(options: ModelDecorationOptions) {
- this.options = options;
- this.isForValidation = (
- this.options.className === ClassName.EditorErrorDecoration
- || this.options.className === ClassName.EditorWarningDecoration
- );
- }
-
- public setRange(multiLineDecorationsMap: { [key: string]: InternalDecoration; }, range: Range): void {
- if (this.range.equalsRange(range)) {
- return;
- }
-
- let rangeWasMultiLine = (this.range.startLineNumber !== this.range.endLineNumber);
- this.range = range;
- let rangeIsMultiline = (this.range.startLineNumber !== this.range.endLineNumber);
-
- if (rangeWasMultiLine === rangeIsMultiline) {
- return;
- }
-
- if (rangeIsMultiline) {
- multiLineDecorationsMap[this.id] = this;
- } else {
- delete multiLineDecorationsMap[this.id];
- }
- }
-}
+import { IntervalNode, IntervalTree, recomputeMaxEnd, getNodeIsInOverviewRuler } from 'vs/editor/common/model/intervalTree';
let _INSTANCE_COUNT = 0;
/**
@@ -129,7 +34,7 @@ function nextInstanceId(): string {
return String.fromCharCode(CharCode.A + result - LETTERS_CNT);
}
-export class TextModelWithDecorations extends TextModelWithMarkers implements editorCommon.ITextModelWithDecorations {
+export class TextModelWithDecorations extends TextModelWithTokens implements editorCommon.ITextModelWithDecorations {
/**
* Used to workaround broken clients that might attempt using a decoration id generated by a different model.
@@ -137,39 +42,26 @@ export class TextModelWithDecorations extends TextModelWithMarkers implements ed
*/
private readonly _instanceId: string;
private _lastDecorationId: number;
-
- private _currentDecorationsTracker: DecorationsTracker;
private _currentDecorationsTrackerCnt: number;
-
- private _currentMarkersTracker: MarkersTracker;
- private _currentMarkersTrackerCnt: number;
-
- private _decorations: { [decorationId: string]: InternalDecoration; };
- private _internalDecorations: { [internalDecorationId: number]: InternalDecoration; };
- private _multiLineDecorationsMap: { [key: string]: InternalDecoration; };
+ private _currentDecorationsTrackerDidChange: boolean;
+ private _decorations: { [decorationId: string]: IntervalNode; };
+ private _decorationsTree: DecorationsTrees;
constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier) {
super(rawTextSource, creationOptions, languageIdentifier);
this._instanceId = nextInstanceId();
this._lastDecorationId = 0;
-
- // Initialize decorations
- this._currentDecorationsTracker = null;
this._currentDecorationsTrackerCnt = 0;
-
- this._currentMarkersTracker = null;
- this._currentMarkersTrackerCnt = 0;
-
+ this._currentDecorationsTrackerDidChange = false;
this._decorations = Object.create(null);
- this._internalDecorations = Object.create(null);
- this._multiLineDecorationsMap = Object.create(null);
+ this._decorationsTree = new DecorationsTrees();
}
public dispose(): void {
this._decorations = null;
- this._internalDecorations = null;
- this._multiLineDecorationsMap = null;
+ this._decorationsTree = null;
+
super.dispose();
}
@@ -178,59 +70,108 @@ export class TextModelWithDecorations extends TextModelWithMarkers implements ed
// Destroy all my decorations
this._decorations = Object.create(null);
- this._internalDecorations = Object.create(null);
- this._multiLineDecorationsMap = Object.create(null);
+ this._decorationsTree = new DecorationsTrees();
+ }
+
+ _getTrackedRangesCount(): number {
+ return this._decorationsTree.count();
}
- private static _shouldStartMarkerSticksToPreviousCharacter(stickiness: editorCommon.TrackedRangeStickiness): boolean {
- if (stickiness === editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges || stickiness === editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore) {
- return true;
+ // --- END TrackedRanges
+
+ protected _acquireDecorationsTracker(): void {
+ if (this._currentDecorationsTrackerCnt === 0) {
+ this._currentDecorationsTrackerDidChange = false;
}
- return false;
+ this._currentDecorationsTrackerCnt++;
}
- private static _shouldEndMarkerSticksToPreviousCharacter(stickiness: editorCommon.TrackedRangeStickiness): boolean {
- if (stickiness === editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges || stickiness === editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore) {
- return true;
+ protected _releaseDecorationsTracker(): void {
+ this._currentDecorationsTrackerCnt--;
+ if (this._currentDecorationsTrackerCnt === 0) {
+ if (this._currentDecorationsTrackerDidChange) {
+ this._emitModelDecorationsChangedEvent();
+ }
}
- return false;
}
- _getTrackedRangesCount(): number {
- return Object.keys(this._decorations).length;
+ protected _adjustDecorationsForEdit(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {
+ this._currentDecorationsTrackerDidChange = true;
+ this._decorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers);
}
- // --- END TrackedRanges
+ protected _onBeforeEOLChange(): void {
+ super._onBeforeEOLChange();
+
+ // Ensure all decorations get their `range` set.
+ const versionId = this.getVersionId();
+ const allDecorations = this._decorationsTree.search(0, false, false, versionId);
+ this._ensureNodesHaveRanges(allDecorations);
+ }
+
+ protected _onAfterEOLChange(): void {
+ super._onAfterEOLChange();
+
+ // Transform back `range` to offsets
+ const versionId = this.getVersionId();
+ const allDecorations = this._decorationsTree.collectNodesPostOrder();
+ for (let i = 0, len = allDecorations.length; i < len; i++) {
+ const node = allDecorations[i];
+
+ const delta = node.cachedAbsoluteStart - node.start;
+
+ const startOffset = this._lineStarts.getAccumulatedValue(node.range.startLineNumber - 2) + node.range.startColumn - 1;
+ const endOffset = this._lineStarts.getAccumulatedValue(node.range.endLineNumber - 2) + node.range.endColumn - 1;
+
+ node.cachedAbsoluteStart = startOffset;
+ node.cachedAbsoluteEnd = endOffset;
+ node.cachedVersionId = versionId;
+
+ node.start = startOffset - delta;
+ node.end = endOffset - delta;
+
+ recomputeMaxEnd(node);
+ }
+ }
public changeDecorations(callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T, ownerId: number = 0): T {
this._assertNotDisposed();
try {
this._eventEmitter.beginDeferredEmit();
- let decorationsTracker = this._acquireDecorationsTracker();
- return this._changeDecorations(decorationsTracker, ownerId, callback);
+ this._acquireDecorationsTracker();
+ return this._changeDecorations(ownerId, callback);
} finally {
this._releaseDecorationsTracker();
this._eventEmitter.endDeferredEmit();
}
}
- private _changeDecorations(decorationsTracker: DecorationsTracker, ownerId: number, callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T): T {
+ private _changeDecorations(ownerId: number, callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T): T {
let changeAccessor: editorCommon.IModelDecorationsChangeAccessor = {
addDecoration: (range: IRange, options: editorCommon.IModelDecorationOptions): string => {
- return this._addDecorationImpl(decorationsTracker, ownerId, this.validateRange(range), _normalizeOptions(options));
+ this._currentDecorationsTrackerDidChange = true;
+ return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0];
},
changeDecoration: (id: string, newRange: IRange): void => {
- this._changeDecorationImpl(decorationsTracker, id, this.validateRange(newRange));
+ this._currentDecorationsTrackerDidChange = true;
+ this._changeDecorationImpl(id, newRange);
},
changeDecorationOptions: (id: string, options: editorCommon.IModelDecorationOptions) => {
- this._changeDecorationOptionsImpl(decorationsTracker, id, _normalizeOptions(options));
+ this._currentDecorationsTrackerDidChange = true;
+ this._changeDecorationOptionsImpl(id, _normalizeOptions(options));
},
removeDecoration: (id: string): void => {
- this._removeDecorationImpl(decorationsTracker, id);
+ this._currentDecorationsTrackerDidChange = true;
+ this._deltaDecorationsImpl(ownerId, [id], []);
},
deltaDecorations: (oldDecorations: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] => {
- return this._deltaDecorationsImpl(decorationsTracker, ownerId, oldDecorations, this._normalizeDeltaDecorations(newDecorations));
+ if (oldDecorations.length === 0 && newDecorations.length === 0) {
+ // nothing to do
+ return [];
+ }
+ this._currentDecorationsTrackerDidChange = true;
+ return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
}
};
let result: T = null;
@@ -252,41 +193,90 @@ export class TextModelWithDecorations extends TextModelWithMarkers implements ed
if (!oldDecorations) {
oldDecorations = [];
}
- return this.changeDecorations((changeAccessor) => {
- return changeAccessor.deltaDecorations(oldDecorations, newDecorations);
- }, ownerId);
- }
+ if (oldDecorations.length === 0 && newDecorations.length === 0) {
+ // nothing to do
+ return [];
+ }
- public removeAllDecorationsWithOwnerId(ownerId: number): void {
- let toRemove: string[] = [];
+ try {
+ this._eventEmitter.beginDeferredEmit();
+ this._acquireDecorationsTracker();
+ this._currentDecorationsTrackerDidChange = true;
+ return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
+ } finally {
+ this._releaseDecorationsTracker();
+ this._eventEmitter.endDeferredEmit();
+ }
+ }
- for (let decorationId in this._decorations) {
- // No `hasOwnProperty` call due to using Object.create(null)
+ _getTrackedRange(id: string): Range {
+ return this.getDecorationRange(id);
+ }
- let decoration = this._decorations[decorationId];
+ _setTrackedRange(id: string, newRange: Range, newStickiness: editorCommon.TrackedRangeStickiness): string {
+ const node = (id ? this._decorations[id] : null);
- if (decoration.ownerId === ownerId) {
- toRemove.push(decoration.id);
+ if (!node) {
+ if (!newRange) {
+ // node doesn't exist, the request is to delete => nothing to do
+ return null;
}
+ // node doesn't exist, the request is to set => add the tracked range
+ return this._deltaDecorationsImpl(0, [], [{ range: newRange, options: TRACKED_RANGE_OPTIONS[newStickiness] }])[0];
}
- this._removeDecorationsImpl(null, toRemove);
+ if (!newRange) {
+ // node exists, the request is to delete => delete node
+ this._decorationsTree.delete(node);
+ delete this._decorations[node.id];
+ return null;
+ }
+
+ // node exists, the request is to set => change the tracked range and its options
+ const range = this._validateRangeRelaxedNoAllocations(newRange);
+ const startOffset = this._lineStarts.getAccumulatedValue(range.startLineNumber - 2) + range.startColumn - 1;
+ const endOffset = this._lineStarts.getAccumulatedValue(range.endLineNumber - 2) + range.endColumn - 1;
+ this._decorationsTree.delete(node);
+ node.reset(this.getVersionId(), startOffset, endOffset, range);
+ node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]);
+ this._decorationsTree.insert(node);
+ return node.id;
+ }
+
+ public removeAllDecorationsWithOwnerId(ownerId: number): void {
+ if (this._isDisposed) {
+ return;
+ }
+ const nodes = this._decorationsTree.collectNodesFromOwner(ownerId);
+ for (let i = 0, len = nodes.length; i < len; i++) {
+ const node = nodes[i];
+
+ this._decorationsTree.delete(node);
+ delete this._decorations[node.id];
+ }
}
public getDecorationOptions(decorationId: string): editorCommon.IModelDecorationOptions {
- let decoration = this._decorations[decorationId];
- if (!decoration) {
+ const node = this._decorations[decorationId];
+ if (!node) {
return null;
}
- return decoration.options;
+ return node.options;
}
public getDecorationRange(decorationId: string): Range {
- let decoration = this._decorations[decorationId];
- if (!decoration) {
+ const node = this._decorations[decorationId];
+ if (!node) {
return null;
}
- return decoration.range;
+ const versionId = this.getVersionId();
+ if (node.cachedVersionId !== versionId) {
+ this._decorationsTree.resolveNode(node, versionId);
+ }
+ if (node.range === null) {
+ node.range = this._getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd);
+ }
+ return node.range;
}
public getLineDecorations(lineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] {
@@ -297,116 +287,6 @@ export class TextModelWithDecorations extends TextModelWithMarkers implements ed
return this.getLinesDecorations(lineNumber, lineNumber, ownerId, filterOutValidation);
}
- /**
- * Fetch only multi-line decorations that intersect with the given line number range
- */
- private _getMultiLineDecorations(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): InternalDecoration[] {
- const filterStartLineNumber = filterRange.startLineNumber;
- const filterStartColumn = filterRange.startColumn;
- const filterEndLineNumber = filterRange.endLineNumber;
- const filterEndColumn = filterRange.endColumn;
-
- let result: InternalDecoration[] = [], resultLen = 0;
-
- for (let decorationId in this._multiLineDecorationsMap) {
- // No `hasOwnProperty` call due to using Object.create(null)
- let decoration = this._multiLineDecorationsMap[decorationId];
-
- if (filterOwnerId && decoration.ownerId && decoration.ownerId !== filterOwnerId) {
- continue;
- }
-
- if (filterOutValidation && decoration.isForValidation) {
- continue;
- }
-
- let range = decoration.range;
-
- if (range.startLineNumber > filterEndLineNumber) {
- continue;
- }
- if (range.startLineNumber === filterEndLineNumber && range.startColumn > filterEndColumn) {
- continue;
- }
- if (range.endLineNumber < filterStartLineNumber) {
- continue;
- }
- if (range.endLineNumber === filterStartLineNumber && range.endColumn < filterStartColumn) {
- continue;
- }
-
- result[resultLen++] = decoration;
- }
-
- return result;
- }
-
- private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): InternalDecoration[] {
- const filterStartLineNumber = filterRange.startLineNumber;
- const filterStartColumn = filterRange.startColumn;
- const filterEndLineNumber = filterRange.endLineNumber;
- const filterEndColumn = filterRange.endColumn;
-
- let result = this._getMultiLineDecorations(filterRange, filterOwnerId, filterOutValidation);
- let resultLen = result.length;
- let resultMap: { [decorationId: string]: boolean; } = {};
-
- for (let i = 0, len = resultLen; i < len; i++) {
- resultMap[result[i].id] = true;
- }
-
- for (let lineNumber = filterStartLineNumber; lineNumber <= filterEndLineNumber; lineNumber++) {
- let lineMarkers = this._lines[lineNumber - 1].getMarkers();
- if (lineMarkers === null) {
- continue;
- }
- for (let i = 0, len = lineMarkers.length; i < len; i++) {
- let lineMarker = lineMarkers[i];
- let internalDecorationId = lineMarker.internalDecorationId;
-
- if (internalDecorationId === 0) {
- // marker does not belong to any decoration
- continue;
- }
-
- let decoration = this._internalDecorations[internalDecorationId];
-
- if (resultMap.hasOwnProperty(decoration.id)) {
- // decoration already in result
- continue;
- }
-
- if (filterOwnerId && decoration.ownerId && decoration.ownerId !== filterOwnerId) {
- continue;
- }
-
- if (filterOutValidation && decoration.isForValidation) {
- continue;
- }
-
- let range = decoration.range;
-
- if (range.startLineNumber > filterEndLineNumber) {
- continue;
- }
- if (range.startLineNumber === filterEndLineNumber && range.startColumn > filterEndColumn) {
- continue;
- }
- if (range.endLineNumber < filterStartLineNumber) {
- continue;
- }
- if (range.endLineNumber === filterStartLineNumber && range.endColumn < filterStartColumn) {
- continue;
- }
-
- result[resultLen++] = decoration;
- resultMap[decoration.id] = true;
- }
- }
-
- return result;
- }
-
public getLinesDecorations(_startLineNumber: number, _endLineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] {
let lineCount = this.getLineCount();
let startLineNumber = Math.min(lineCount, Math.max(1, _startLineNumber));
@@ -415,426 +295,235 @@ export class TextModelWithDecorations extends TextModelWithMarkers implements ed
return this._getDecorationsInRange(new Range(startLineNumber, 1, endLineNumber, endColumn), ownerId, filterOutValidation);
}
- public getDecorationsInRange(range: IRange, ownerId?: number, filterOutValidation?: boolean): editorCommon.IModelDecoration[] {
+ public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] {
let validatedRange = this.validateRange(range);
return this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation);
}
- public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] {
- let result: InternalDecoration[] = [], resultLen = 0;
-
- for (let decorationId in this._decorations) {
- // No `hasOwnProperty` call due to using Object.create(null)
- let decoration = this._decorations[decorationId];
-
- if (ownerId && decoration.ownerId && decoration.ownerId !== ownerId) {
- continue;
- }
-
- if (filterOutValidation && decoration.isForValidation) {
- continue;
- }
-
- result[resultLen++] = decoration;
- }
-
- return result;
+ public getOverviewRulerDecorations(ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] {
+ const versionId = this.getVersionId();
+ const result = this._decorationsTree.search(ownerId, filterOutValidation, true, versionId);
+ return this._ensureNodesHaveRanges(result);
}
- protected _acquireMarkersTracker(): MarkersTracker {
- if (this._currentMarkersTrackerCnt === 0) {
- this._currentMarkersTracker = new MarkersTracker();
- }
- this._currentMarkersTrackerCnt++;
- return this._currentMarkersTracker;
+ public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] {
+ const versionId = this.getVersionId();
+ const result = this._decorationsTree.search(ownerId, filterOutValidation, false, versionId);
+ return this._ensureNodesHaveRanges(result);
}
- protected _releaseMarkersTracker(): void {
- this._currentMarkersTrackerCnt--;
- if (this._currentMarkersTrackerCnt === 0) {
- let markersTracker = this._currentMarkersTracker;
- this._currentMarkersTracker = null;
- this._handleTrackedMarkers(markersTracker);
+ private _emitModelDecorationsChangedEvent(): void {
+ if (!this._isDisposing) {
+ let e: textModelEvents.IModelDecorationsChangedEvent = {};
+ this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelDecorationsChanged, e);
}
}
- /**
- * Handle changed markers (i.e. update decorations ranges and return the changed decorations, unique and sorted by id)
- */
- private _handleTrackedMarkers(markersTracker: MarkersTracker): void {
- let changedInternalDecorationIds = markersTracker.getDecorationIds();
- if (changedInternalDecorationIds.length === 0) {
- return;
- }
+ private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): IntervalNode[] {
+ const startOffset = this._lineStarts.getAccumulatedValue(filterRange.startLineNumber - 2) + filterRange.startColumn - 1;
+ const endOffset = this._lineStarts.getAccumulatedValue(filterRange.endLineNumber - 2) + filterRange.endColumn - 1;
- changedInternalDecorationIds.sort();
+ const versionId = this.getVersionId();
+ const result = this._decorationsTree.intervalSearch(startOffset, endOffset, filterOwnerId, filterOutValidation, versionId);
- let uniqueChangedDecorations: string[] = [], uniqueChangedDecorationsLen = 0;
- let previousInternalDecorationId: number = 0;
- for (let i = 0, len = changedInternalDecorationIds.length; i < len; i++) {
- let internalDecorationId = changedInternalDecorationIds[i];
- if (internalDecorationId === previousInternalDecorationId) {
- continue;
- }
- previousInternalDecorationId = internalDecorationId;
+ return this._ensureNodesHaveRanges(result);
+ }
- let decoration = this._internalDecorations[internalDecorationId];
- if (!decoration) {
- // perhaps the decoration was removed in the meantime
- continue;
+ private _ensureNodesHaveRanges(nodes: IntervalNode[]): IntervalNode[] {
+ for (let i = 0, len = nodes.length; i < len; i++) {
+ const node = nodes[i];
+ if (node.range === null) {
+ node.range = this._getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd);
}
-
- let startMarker = decoration.startMarker.position;
- let endMarker = decoration.endMarker.position;
- let range = TextModelWithDecorations._createRangeFromMarkers(startMarker, endMarker);
- decoration.setRange(this._multiLineDecorationsMap, range);
-
- uniqueChangedDecorations[uniqueChangedDecorationsLen++] = decoration.id;
- }
-
- if (uniqueChangedDecorations.length > 0) {
- let e: textModelEvents.IModelDecorationsChangedEvent = {
- addedDecorations: [],
- changedDecorations: uniqueChangedDecorations,
- removedDecorations: []
- };
- this.emitModelDecorationsChangedEvent(e);
}
+ return nodes;
}
- private static _createRangeFromMarkers(startPosition: Position, endPosition: Position): Range {
- if (endPosition.isBefore(startPosition)) {
- // This tracked range has turned in on itself (end marker before start marker)
- // This can happen in extreme editing conditions where lots of text is removed and lots is added
+ private _getRangeAt(start: number, end: number): Range {
+ const startResult = this._lineStarts.getIndexOf(start);
+ const startLineLength = this._lines[startResult.index].text.length;
+ const startColumn = Math.min(startResult.remainder + 1, startLineLength + 1);
- // Treat it as a collapsed range
- return new Range(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column);
- }
- return new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
- }
+ const endResult = this._lineStarts.getIndexOf(end);
+ const endLineLength = this._lines[endResult.index].text.length;
+ const endColumn = Math.min(endResult.remainder + 1, endLineLength + 1);
- private _acquireDecorationsTracker(): DecorationsTracker {
- if (this._currentDecorationsTrackerCnt === 0) {
- this._currentDecorationsTracker = new DecorationsTracker();
- }
- this._currentDecorationsTrackerCnt++;
- return this._currentDecorationsTracker;
+ return new Range(startResult.index + 1, startColumn, endResult.index + 1, endColumn);
}
- private _releaseDecorationsTracker(): void {
- this._currentDecorationsTrackerCnt--;
- if (this._currentDecorationsTrackerCnt === 0) {
- let decorationsTracker = this._currentDecorationsTracker;
- this._currentDecorationsTracker = null;
- this._handleTrackedDecorations(decorationsTracker);
- }
- }
-
- private _handleTrackedDecorations(decorationsTracker: DecorationsTracker): void {
- if (
- decorationsTracker.addedDecorationsLen === 0
- && decorationsTracker.changedDecorationsLen === 0
- && decorationsTracker.removedDecorationsLen === 0
- ) {
+ private _changeDecorationImpl(decorationId: string, _range: IRange): void {
+ const node = this._decorations[decorationId];
+ if (!node) {
return;
}
+ const range = this._validateRangeRelaxedNoAllocations(_range);
+ const startOffset = this._lineStarts.getAccumulatedValue(range.startLineNumber - 2) + range.startColumn - 1;
+ const endOffset = this._lineStarts.getAccumulatedValue(range.endLineNumber - 2) + range.endColumn - 1;
- let e: textModelEvents.IModelDecorationsChangedEvent = {
- addedDecorations: decorationsTracker.addedDecorations,
- changedDecorations: decorationsTracker.changedDecorations,
- removedDecorations: decorationsTracker.removedDecorations
- };
- this.emitModelDecorationsChangedEvent(e);
+ this._decorationsTree.delete(node);
+ node.reset(this.getVersionId(), startOffset, endOffset, range);
+ this._decorationsTree.insert(node);
}
- private emitModelDecorationsChangedEvent(e: textModelEvents.IModelDecorationsChangedEvent): void {
- if (!this._isDisposing) {
- this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelDecorationsChanged, e);
+ private _changeDecorationOptionsImpl(decorationId: string, options: ModelDecorationOptions): void {
+ const node = this._decorations[decorationId];
+ if (!node) {
+ return;
}
- }
- private _normalizeDeltaDecorations(deltaDecorations: editorCommon.IModelDeltaDecoration[]): ModelDeltaDecoration[] {
- let result: ModelDeltaDecoration[] = [];
- for (let i = 0, len = deltaDecorations.length; i < len; i++) {
- let deltaDecoration = deltaDecorations[i];
- result.push(new ModelDeltaDecoration(i, this.validateRange(deltaDecoration.range), _normalizeOptions(deltaDecoration.options)));
- }
- return result;
- }
+ const nodeWasInOverviewRuler = (node.options.overviewRuler.color ? true : false);
+ const nodeIsInOverviewRuler = (options.overviewRuler.color ? true : false);
- private _externalDecorationId(internalId: number): string {
- return `${this._instanceId};${internalId}`;
+ if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) {
+ // Delete + Insert due to an overview ruler status change
+ this._decorationsTree.delete(node);
+ node.setOptions(options);
+ this._decorationsTree.insert(node);
+ } else {
+ node.setOptions(options);
+ }
}
- private _addDecorationImpl(decorationsTracker: DecorationsTracker, ownerId: number, _range: Range, options: ModelDecorationOptions): string {
- let range = this.validateRange(_range);
-
- let internalDecorationId = (++this._lastDecorationId);
- let decorationId = this._externalDecorationId(internalDecorationId);
-
- let markers = this._addMarkers([
- {
- internalDecorationId: internalDecorationId,
- position: new Position(range.startLineNumber, range.startColumn),
- stickToPreviousCharacter: TextModelWithDecorations._shouldStartMarkerSticksToPreviousCharacter(options.stickiness)
- },
- {
- internalDecorationId: internalDecorationId,
- position: new Position(range.endLineNumber, range.endColumn),
- stickToPreviousCharacter: TextModelWithDecorations._shouldEndMarkerSticksToPreviousCharacter(options.stickiness)
- }
- ]);
+ private _deltaDecorationsImpl(ownerId: number, oldDecorationsIds: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] {
+ const versionId = this.getVersionId();
- let decoration = new InternalDecoration(decorationId, internalDecorationId, ownerId, range, markers[0], markers[1], options);
- this._decorations[decorationId] = decoration;
- this._internalDecorations[internalDecorationId] = decoration;
- if (range.startLineNumber !== range.endLineNumber) {
- this._multiLineDecorationsMap[decorationId] = decoration;
- }
+ const oldDecorationsLen = oldDecorationsIds.length;
+ let oldDecorationIndex = 0;
- decorationsTracker.addNewDecoration(decorationId);
+ const newDecorationsLen = newDecorations.length;
+ let newDecorationIndex = 0;
- return decorationId;
- }
+ let result = new Array(newDecorationsLen);
+ while (oldDecorationIndex < oldDecorationsLen || newDecorationIndex < newDecorationsLen) {
- private _addDecorationsImpl(decorationsTracker: DecorationsTracker, ownerId: number, newDecorations: ModelDeltaDecoration[]): string[] {
- let internalDecorationIds: number[] = [];
- let decorationIds: string[] = [];
- let newMarkers: INewMarker[] = [];
+ let node: IntervalNode = null;
- for (let i = 0, len = newDecorations.length; i < len; i++) {
- let newDecoration = newDecorations[i];
- let range = newDecoration.range;
- let stickiness = newDecoration.options.stickiness;
+ if (oldDecorationIndex < oldDecorationsLen) {
+ // (1) get ourselves an old node
+ do {
+ node = this._decorations[oldDecorationsIds[oldDecorationIndex++]];
+ } while (!node && oldDecorationIndex < oldDecorationsLen);
- let internalDecorationId = (++this._lastDecorationId);
- let decorationId = this._externalDecorationId(internalDecorationId);
+ // (2) remove the node from the tree (if it exists)
+ if (node) {
+ this._decorationsTree.delete(node);
+ }
+ }
- internalDecorationIds[i] = internalDecorationId;
- decorationIds[i] = decorationId;
+ if (newDecorationIndex < newDecorationsLen) {
+ // (3) create a new node if necessary
+ if (!node) {
+ const internalDecorationId = (++this._lastDecorationId);
+ const decorationId = `${this._instanceId};${internalDecorationId}`;
+ node = new IntervalNode(decorationId, 0, 0);
+ this._decorations[decorationId] = node;
+ }
- newMarkers[2 * i] = {
- internalDecorationId: internalDecorationId,
- position: new Position(range.startLineNumber, range.startColumn),
- stickToPreviousCharacter: TextModelWithDecorations._shouldStartMarkerSticksToPreviousCharacter(stickiness)
- };
+ // (4) initialize node
+ const newDecoration = newDecorations[newDecorationIndex];
+ const range = this._validateRangeRelaxedNoAllocations(newDecoration.range);
+ const options = _normalizeOptions(newDecoration.options);
+ const startOffset = this._lineStarts.getAccumulatedValue(range.startLineNumber - 2) + range.startColumn - 1;
+ const endOffset = this._lineStarts.getAccumulatedValue(range.endLineNumber - 2) + range.endColumn - 1;
- newMarkers[2 * i + 1] = {
- internalDecorationId: internalDecorationId,
- position: new Position(range.endLineNumber, range.endColumn),
- stickToPreviousCharacter: TextModelWithDecorations._shouldEndMarkerSticksToPreviousCharacter(stickiness)
- };
- }
+ node.ownerId = ownerId;
+ node.reset(versionId, startOffset, endOffset, range);
+ node.setOptions(options);
- let markerIds = this._addMarkers(newMarkers);
+ this._decorationsTree.insert(node);
- for (let i = 0, len = newDecorations.length; i < len; i++) {
- let newDecoration = newDecorations[i];
- let range = newDecoration.range;
- let internalDecorationId = internalDecorationIds[i];
- let decorationId = decorationIds[i];
- let startMarker = markerIds[2 * i];
- let endMarker = markerIds[2 * i + 1];
+ result[newDecorationIndex] = node.id;
- let decoration = new InternalDecoration(decorationId, internalDecorationId, ownerId, range, startMarker, endMarker, newDecoration.options);
- this._decorations[decorationId] = decoration;
- this._internalDecorations[internalDecorationId] = decoration;
- if (range.startLineNumber !== range.endLineNumber) {
- this._multiLineDecorationsMap[decorationId] = decoration;
+ newDecorationIndex++;
+ } else {
+ if (node) {
+ delete this._decorations[node.id];
+ }
}
-
- decorationsTracker.addNewDecoration(decorationId);
}
- return decorationIds;
+ return result;
}
+}
- private _changeDecorationImpl(decorationsTracker: DecorationsTracker, decorationId: string, newRange: Range): void {
- let decoration = this._decorations[decorationId];
- if (!decoration) {
- return;
- }
-
- let startMarker = decoration.startMarker;
- if (newRange.startLineNumber !== startMarker.position.lineNumber) {
- // move marker between lines
- this._lines[startMarker.position.lineNumber - 1].removeMarker(startMarker);
- this._lines[newRange.startLineNumber - 1].addMarker(startMarker);
- }
- startMarker.setPosition(new Position(newRange.startLineNumber, newRange.startColumn));
+class DecorationsTrees {
- let endMarker = decoration.endMarker;
- if (newRange.endLineNumber !== endMarker.position.lineNumber) {
- // move marker between lines
- this._lines[endMarker.position.lineNumber - 1].removeMarker(endMarker);
- this._lines[newRange.endLineNumber - 1].addMarker(endMarker);
- }
- endMarker.setPosition(new Position(newRange.endLineNumber, newRange.endColumn));
+ /**
+ * This tree holds decorations that do not show up in the overview ruler.
+ */
+ private _decorationsTree0: IntervalTree;
- decoration.setRange(this._multiLineDecorationsMap, newRange);
+ /**
+ * This tree holds decorations that show up in the overview ruler.
+ */
+ private _decorationsTree1: IntervalTree;
- decorationsTracker.addMovedDecoration(decorationId);
+ constructor() {
+ this._decorationsTree0 = new IntervalTree();
+ this._decorationsTree1 = new IntervalTree();
}
- private _changeDecorationOptionsImpl(decorationsTracker: DecorationsTracker, decorationId: string, options: ModelDecorationOptions): void {
- let decoration = this._decorations[decorationId];
- if (!decoration) {
- return;
- }
-
- if (decoration.options.stickiness !== options.stickiness) {
- decoration.startMarker.stickToPreviousCharacter = TextModelWithDecorations._shouldStartMarkerSticksToPreviousCharacter(options.stickiness);
- decoration.endMarker.stickToPreviousCharacter = TextModelWithDecorations._shouldEndMarkerSticksToPreviousCharacter(options.stickiness);
- }
-
- decoration.setOptions(options);
-
- decorationsTracker.addUpdatedDecoration(decorationId);
+ public intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] {
+ const r0 = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId);
+ const r1 = this._decorationsTree1.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId);
+ return r0.concat(r1);
}
- private _removeDecorationImpl(decorationsTracker: DecorationsTracker, decorationId: string): void {
- let decoration = this._decorations[decorationId];
- if (!decoration) {
- return;
- }
-
- this._removeMarkers([decoration.startMarker, decoration.endMarker]);
-
- delete this._multiLineDecorationsMap[decorationId];
- delete this._decorations[decorationId];
- delete this._internalDecorations[decoration.internalId];
-
- if (decorationsTracker) {
- decorationsTracker.addRemovedDecoration(decorationId);
+ public search(filterOwnerId: number, filterOutValidation: boolean, overviewRulerOnly: boolean, cachedVersionId: number): IntervalNode[] {
+ if (overviewRulerOnly) {
+ return this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId);
+ } else {
+ const r0 = this._decorationsTree0.search(filterOwnerId, filterOutValidation, cachedVersionId);
+ const r1 = this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId);
+ return r0.concat(r1);
}
}
- private _removeDecorationsImpl(decorationsTracker: DecorationsTracker, decorationIds: string[]): void {
- let removeMarkers: LineMarker[] = [], removeMarkersLen = 0;
-
- for (let i = 0, len = decorationIds.length; i < len; i++) {
- let decorationId = decorationIds[i];
- let decoration = this._decorations[decorationId];
- if (!decoration) {
- continue;
- }
-
- if (decorationsTracker) {
- decorationsTracker.addRemovedDecoration(decorationId);
- }
-
- removeMarkers[removeMarkersLen++] = decoration.startMarker;
- removeMarkers[removeMarkersLen++] = decoration.endMarker;
- delete this._multiLineDecorationsMap[decorationId];
- delete this._decorations[decorationId];
- delete this._internalDecorations[decoration.internalId];
- }
-
- if (removeMarkers.length > 0) {
- this._removeMarkers(removeMarkers);
- }
+ public count(): number {
+ const c0 = this._decorationsTree0.count();
+ const c1 = this._decorationsTree1.count();
+ return c0 + c1;
}
- private _resolveOldDecorations(oldDecorations: string[]): InternalDecoration[] {
- let result: InternalDecoration[] = [];
- for (let i = 0, len = oldDecorations.length; i < len; i++) {
- let id = oldDecorations[i];
- let decoration = this._decorations[id];
- if (!decoration) {
- continue;
- }
-
- result.push(decoration);
- }
- return result;
+ public collectNodesFromOwner(ownerId: number): IntervalNode[] {
+ const r0 = this._decorationsTree0.collectNodesFromOwner(ownerId);
+ const r1 = this._decorationsTree1.collectNodesFromOwner(ownerId);
+ return r0.concat(r1);
}
- private _deltaDecorationsImpl(decorationsTracker: DecorationsTracker, ownerId: number, oldDecorationsIds: string[], newDecorations: ModelDeltaDecoration[]): string[] {
-
- if (oldDecorationsIds.length === 0) {
- // Nothing to remove
- return this._addDecorationsImpl(decorationsTracker, ownerId, newDecorations);
- }
-
- if (newDecorations.length === 0) {
- // Nothing to add
- this._removeDecorationsImpl(decorationsTracker, oldDecorationsIds);
- return [];
- }
-
- let oldDecorations = this._resolveOldDecorations(oldDecorationsIds);
-
- oldDecorations.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
- newDecorations.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
-
- let result: string[] = [],
- oldDecorationsIndex = 0,
- oldDecorationsLength = oldDecorations.length,
- newDecorationsIndex = 0,
- newDecorationsLength = newDecorations.length,
- decorationsToAdd: ModelDeltaDecoration[] = [],
- decorationsToRemove: string[] = [];
-
- while (oldDecorationsIndex < oldDecorationsLength && newDecorationsIndex < newDecorationsLength) {
- let oldDecoration = oldDecorations[oldDecorationsIndex];
- let newDecoration = newDecorations[newDecorationsIndex];
- let comparison = Range.compareRangesUsingStarts(oldDecoration.range, newDecoration.range);
-
- if (comparison < 0) {
- // `oldDecoration` is before `newDecoration` => remove `oldDecoration`
- decorationsToRemove.push(oldDecoration.id);
- oldDecorationsIndex++;
- continue;
- }
-
- if (comparison > 0) {
- // `newDecoration` is before `oldDecoration` => add `newDecoration`
- decorationsToAdd.push(newDecoration);
- newDecorationsIndex++;
- continue;
- }
-
- // The ranges of `oldDecoration` and `newDecoration` are equal
-
- if (!oldDecoration.options.equals(newDecoration.options)) {
- // The options do not match => remove `oldDecoration`
- decorationsToRemove.push(oldDecoration.id);
- oldDecorationsIndex++;
- continue;
- }
-
- // Bingo! We can reuse `oldDecoration` for `newDecoration`
- result[newDecoration.index] = oldDecoration.id;
- oldDecorationsIndex++;
- newDecorationsIndex++;
- }
-
- while (oldDecorationsIndex < oldDecorationsLength) {
- // No more new decorations => remove decoration at `oldDecorationsIndex`
- decorationsToRemove.push(oldDecorations[oldDecorationsIndex].id);
- oldDecorationsIndex++;
- }
+ public collectNodesPostOrder(): IntervalNode[] {
+ const r0 = this._decorationsTree0.collectNodesPostOrder();
+ const r1 = this._decorationsTree1.collectNodesPostOrder();
+ return r0.concat(r1);
+ }
- while (newDecorationsIndex < newDecorationsLength) {
- // No more old decorations => add decoration at `newDecorationsIndex`
- decorationsToAdd.push(newDecorations[newDecorationsIndex]);
- newDecorationsIndex++;
+ public insert(node: IntervalNode): void {
+ if (getNodeIsInOverviewRuler(node)) {
+ this._decorationsTree1.insert(node);
+ } else {
+ this._decorationsTree0.insert(node);
}
+ }
- // Remove `decorationsToRemove`
- if (decorationsToRemove.length > 0) {
- this._removeDecorationsImpl(decorationsTracker, decorationsToRemove);
+ public delete(node: IntervalNode): void {
+ if (getNodeIsInOverviewRuler(node)) {
+ this._decorationsTree1.delete(node);
+ } else {
+ this._decorationsTree0.delete(node);
}
+ }
- // Add `decorationsToAdd`
- if (decorationsToAdd.length > 0) {
- let newIds = this._addDecorationsImpl(decorationsTracker, ownerId, decorationsToAdd);
- for (let i = 0, len = decorationsToAdd.length; i < len; i++) {
- result[decorationsToAdd[i].index] = newIds[i];
- }
+ public resolveNode(node: IntervalNode, cachedVersionId: number): void {
+ if (getNodeIsInOverviewRuler(node)) {
+ this._decorationsTree1.resolveNode(node, cachedVersionId);
+ } else {
+ this._decorationsTree0.resolveNode(node, cachedVersionId);
}
+ }
- return result;
+ public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {
+ this._decorationsTree0.acceptReplace(offset, length, textLength, forceMoveMarkers);
+ this._decorationsTree1.acceptReplace(offset, length, textLength, forceMoveMarkers);
}
}
@@ -847,12 +536,14 @@ export class ModelDecorationOverviewRulerOptions implements editorCommon.IModelD
readonly darkColor: string | ThemeColor;
readonly hcColor: string | ThemeColor;
readonly position: editorCommon.OverviewRulerLane;
+ _resolvedColor: string;
constructor(options: editorCommon.IModelDecorationOverviewRulerOptions) {
this.color = strings.empty;
this.darkColor = strings.empty;
this.hcColor = strings.empty;
this.position = editorCommon.OverviewRulerLane.Center;
+ this._resolvedColor = null;
if (options && options.color) {
this.color = options.color;
@@ -949,18 +640,15 @@ export class ModelDecorationOptions implements editorCommon.IModelDecorationOpti
}
ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({});
-class ModelDeltaDecoration implements editorCommon.IModelDeltaDecoration {
-
- index: number;
- range: Range;
- options: ModelDecorationOptions;
-
- constructor(index: number, range: Range, options: ModelDecorationOptions) {
- this.index = index;
- this.range = range;
- this.options = options;
- }
-}
+/**
+ * The order carefully matches the values of the enum.
+ */
+const TRACKED_RANGE_OPTIONS = [
+ ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }),
+ ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }),
+ ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore }),
+ ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter }),
+];
function _normalizeOptions(options: editorCommon.IModelDecorationOptions): ModelDecorationOptions {
if (options instanceof ModelDecorationOptions) {
diff --git a/src/vs/editor/common/model/textModelWithMarkers.ts b/src/vs/editor/common/model/textModelWithMarkers.ts
deleted file mode 100644
index 6100bfc399924..0000000000000
--- a/src/vs/editor/common/model/textModelWithMarkers.ts
+++ /dev/null
@@ -1,174 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-'use strict';
-
-import { IdGenerator } from 'vs/base/common/idGenerator';
-import { Position } from 'vs/editor/common/core/position';
-import { ITextModelWithMarkers, ITextModelCreationOptions } from 'vs/editor/common/editorCommon';
-import { LineMarker } from 'vs/editor/common/model/modelLine';
-import { TextModelWithTokens } from 'vs/editor/common/model/textModelWithTokens';
-import { LanguageIdentifier } from 'vs/editor/common/modes';
-import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource';
-
-export interface IMarkerIdToMarkerMap {
- [key: string]: LineMarker;
-}
-
-export interface INewMarker {
- internalDecorationId: number;
- position: Position;
- stickToPreviousCharacter: boolean;
-}
-
-var _INSTANCE_COUNT = 0;
-
-export class TextModelWithMarkers extends TextModelWithTokens implements ITextModelWithMarkers {
-
- private _markerIdGenerator: IdGenerator;
- protected _markerIdToMarker: IMarkerIdToMarkerMap;
-
- constructor(rawTextSource: IRawTextSource, creationOptions: ITextModelCreationOptions, languageIdentifier: LanguageIdentifier) {
- super(rawTextSource, creationOptions, languageIdentifier);
- this._markerIdGenerator = new IdGenerator((++_INSTANCE_COUNT) + ';');
- this._markerIdToMarker = Object.create(null);
- }
-
- public dispose(): void {
- this._markerIdToMarker = null;
- super.dispose();
- }
-
- protected _resetValue(newValue: ITextSource): void {
- super._resetValue(newValue);
-
- // Destroy all my markers
- this._markerIdToMarker = Object.create(null);
- }
-
- _addMarker(internalDecorationId: number, lineNumber: number, column: number, stickToPreviousCharacter: boolean): string {
- var pos = this.validatePosition(new Position(lineNumber, column));
-
- var marker = new LineMarker(this._markerIdGenerator.nextId(), internalDecorationId, pos, stickToPreviousCharacter);
- this._markerIdToMarker[marker.id] = marker;
-
- this._lines[pos.lineNumber - 1].addMarker(marker);
-
- return marker.id;
- }
-
- protected _addMarkers(newMarkers: INewMarker[]): LineMarker[] {
- if (newMarkers.length === 0) {
- return [];
- }
-
- let markers: LineMarker[] = [];
- for (let i = 0, len = newMarkers.length; i < len; i++) {
- let newMarker = newMarkers[i];
-
- let marker = new LineMarker(this._markerIdGenerator.nextId(), newMarker.internalDecorationId, newMarker.position, newMarker.stickToPreviousCharacter);
- this._markerIdToMarker[marker.id] = marker;
-
- markers[i] = marker;
- }
-
- let sortedMarkers = markers.slice(0);
- sortedMarkers.sort((a, b) => {
- return a.position.lineNumber - b.position.lineNumber;
- });
-
- let currentLineNumber = 0;
- let currentMarkers: LineMarker[] = [], currentMarkersLen = 0;
- for (let i = 0, len = sortedMarkers.length; i < len; i++) {
- let marker = sortedMarkers[i];
-
- if (marker.position.lineNumber !== currentLineNumber) {
- if (currentLineNumber !== 0) {
- this._lines[currentLineNumber - 1].addMarkers(currentMarkers);
- }
- currentLineNumber = marker.position.lineNumber;
- currentMarkers.length = 0;
- currentMarkersLen = 0;
- }
-
- currentMarkers[currentMarkersLen++] = marker;
- }
- this._lines[currentLineNumber - 1].addMarkers(currentMarkers);
-
- return markers;
- }
-
- _changeMarker(id: string, lineNumber: number, column: number): void {
- let marker = this._markerIdToMarker[id];
- if (!marker) {
- return;
- }
-
- let newPos = this.validatePosition(new Position(lineNumber, column));
-
- if (newPos.lineNumber !== marker.position.lineNumber) {
- // Move marker between lines
- this._lines[marker.position.lineNumber - 1].removeMarker(marker);
- this._lines[newPos.lineNumber - 1].addMarker(marker);
- }
-
- marker.setPosition(newPos);
- }
-
- _changeMarkerStickiness(id: string, newStickToPreviousCharacter: boolean): void {
- let marker = this._markerIdToMarker[id];
- if (!marker) {
- return;
- }
-
- marker.stickToPreviousCharacter = newStickToPreviousCharacter;
- }
-
- _getMarker(id: string): Position {
- let marker = this._markerIdToMarker[id];
- if (!marker) {
- return null;
- }
-
- return marker.position;
- }
-
- _getMarkersCount(): number {
- return Object.keys(this._markerIdToMarker).length;
- }
-
- _removeMarker(id: string): void {
- let marker = this._markerIdToMarker[id];
- if (!marker) {
- return;
- }
-
- this._lines[marker.position.lineNumber - 1].removeMarker(marker);
- delete this._markerIdToMarker[id];
- }
-
- protected _removeMarkers(markers: LineMarker[]): void {
- markers.sort((a, b) => {
- return a.position.lineNumber - b.position.lineNumber;
- });
-
- let currentLineNumber = 0;
- let currentMarkers: { [markerId: string]: boolean; } = null;
- for (let i = 0, len = markers.length; i < len; i++) {
- let marker = markers[i];
- delete this._markerIdToMarker[marker.id];
-
- if (marker.position.lineNumber !== currentLineNumber) {
- if (currentLineNumber !== 0) {
- this._lines[currentLineNumber - 1].removeMarkers(currentMarkers);
- }
- currentLineNumber = marker.position.lineNumber;
- currentMarkers = Object.create(null);
- }
-
- currentMarkers[marker.id] = true;
- }
- this._lines[currentLineNumber - 1].removeMarkers(currentMarkers);
- }
-}
diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts
index 1fbc491f2715c..caaa8470b7e68 100644
--- a/src/vs/editor/common/services/modelServiceImpl.ts
+++ b/src/vs/editor/common/services/modelServiceImpl.ts
@@ -26,7 +26,7 @@ import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry';
import { IRawTextSource, TextSource, RawTextSource, ITextSource } from 'vs/editor/common/model/textSource';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
-import { ClassName } from 'vs/editor/common/model/textModelWithDecorations';
+import { ClassName } from 'vs/editor/common/model/intervalTree';
import { ISequence, LcsDiff } from 'vs/base/common/diff/diff';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService';
diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts
index 5cf9d2c728a9e..f57572f1e38b8 100644
--- a/src/vs/editor/common/viewModel/splitLinesCollection.ts
+++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts
@@ -10,10 +10,12 @@ import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer';
-import { ViewLineData, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
+import { ViewLineData, ICoordinatesConverter, IOverviewRulerDecorations } from 'vs/editor/common/viewModel/viewModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
-import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
+import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModelWithDecorations';
+import { ThemeColor, ITheme } from 'vs/platform/theme/common/themeService';
+import { Color } from 'vs/base/common/color';
export class OutputPosition {
_outputPositionBrand: void;
@@ -57,6 +59,7 @@ export interface ISplitLine {
getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number;
getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position;
+ getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number;
}
export interface IViewModelLinesCollection {
@@ -82,6 +85,9 @@ export interface IViewModelLinesCollection {
getViewLineMaxColumn(viewLineNumber: number): number;
getViewLineData(viewLineNumber: number): ViewLineData;
getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[];
+
+ getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): IOverviewRulerDecorations;
+ getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): editorCommon.IModelDecoration[];
}
export class CoordinatesConverter implements ICoordinatesConverter {
@@ -650,6 +656,83 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r);
return r;
}
+
+ private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number {
+ let lineIndex = inputLineNumber - 1;
+ if (this.lines[lineIndex].isVisible()) {
+ // this model line is visible
+ const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
+ return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, inputColumn);
+ }
+
+ // this model line is not visible
+ while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) {
+ lineIndex--;
+ }
+ if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) {
+ // Could not reach a real line
+ return 1;
+ }
+ const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
+ return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1));
+ }
+
+ public getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): IOverviewRulerDecorations {
+ const decorations = this.model.getOverviewRulerDecorations(ownerId, filterOutValidation);
+ const result = new OverviewRulerDecorations();
+ for (let i = 0, len = decorations.length; i < len; i++) {
+ const decoration = decorations[i];
+ const opts = decoration.options.overviewRuler;
+ const lane = opts.position;
+ if (lane === 0) {
+ continue;
+ }
+ const color = resolveColor(opts, theme);
+ const viewStartLineNumber = this._getViewLineNumberForModelPosition(decoration.range.startLineNumber, decoration.range.startColumn);
+ const viewEndLineNumber = this._getViewLineNumberForModelPosition(decoration.range.endLineNumber, decoration.range.endColumn);
+
+ result.accept(color, viewStartLineNumber, viewEndLineNumber, lane);
+ }
+ return result.result;
+ }
+
+ public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): editorCommon.IModelDecoration[] {
+ const modelStart = this.convertViewPositionToModelPosition(range.startLineNumber, range.startColumn);
+ const modelEnd = this.convertViewPositionToModelPosition(range.endLineNumber, range.endColumn);
+
+ if (modelEnd.lineNumber - modelStart.lineNumber <= range.endLineNumber - range.startLineNumber) {
+ // most likely there are no hidden lines => fast path
+ return this.model.getDecorationsInRange(new Range(modelStart.lineNumber, modelStart.column, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation);
+ }
+
+ let result: editorCommon.IModelDecoration[] = [];
+ const modelStartLineIndex = modelStart.lineNumber - 1;
+ const modelEndLineIndex = modelEnd.lineNumber - 1;
+
+ let reqStart: Position = null;
+ for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) {
+ const line = this.lines[modelLineIndex];
+ if (line.isVisible()) {
+ // merge into previous request
+ if (reqStart === null) {
+ reqStart = new Position(modelLineIndex + 1, modelLineIndex === modelStartLineIndex ? modelStart.column : 1);
+ }
+ } else {
+ // hit invisible line => flush request
+ if (reqStart !== null) {
+ result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelLineIndex + 1, 1)));
+ reqStart = null;
+ }
+ }
+ }
+
+ if (reqStart !== null) {
+ result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelEnd.lineNumber, modelEnd.column)));
+ reqStart = null;
+ }
+
+ return result;
+ }
}
class VisibleIdentitySplitLine implements ISplitLine {
@@ -711,6 +794,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position {
return new Position(deltaLineNumber, inputColumn);
}
+
+ public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number {
+ return deltaLineNumber;
+ }
}
class InvisibleIdentitySplitLine implements ISplitLine {
@@ -761,6 +848,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position {
throw new Error('Not supported');
}
+
+ public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number {
+ throw new Error('Not supported');
+ }
}
export class SplitLine implements ISplitLine {
@@ -914,6 +1005,14 @@ export class SplitLine implements ISplitLine {
// console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn);
return new Position(deltaLineNumber + outputLineIndex, outputColumn);
}
+
+ public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number {
+ if (!this._isVisible) {
+ throw new Error('Not supported');
+ }
+ const r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1);
+ return (deltaLineNumber + r.outputLineIndex);
+ }
}
function createSplitLine(linePositionMapperFactory: ILineMapperFactory, text: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, isVisible: boolean): ISplitLine {
@@ -1093,4 +1192,77 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return result;
}
+
+ public getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): IOverviewRulerDecorations {
+ const decorations = this.model.getOverviewRulerDecorations(ownerId, filterOutValidation);
+ const result = new OverviewRulerDecorations();
+ for (let i = 0, len = decorations.length; i < len; i++) {
+ const decoration = decorations[i];
+ const opts = decoration.options.overviewRuler;
+ const lane = opts.position;
+ if (lane === 0) {
+ continue;
+ }
+ const color = resolveColor(opts, theme);
+ const viewStartLineNumber = decoration.range.startLineNumber;
+ const viewEndLineNumber = decoration.range.endLineNumber;
+
+ result.accept(color, viewStartLineNumber, viewEndLineNumber, lane);
+ }
+ return result.result;
+ }
+
+ public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): editorCommon.IModelDecoration[] {
+ return this.model.getDecorationsInRange(range, ownerId, filterOutValidation);
+ }
+}
+
+class OverviewRulerDecorations {
+
+ readonly result: IOverviewRulerDecorations = Object.create(null);
+
+ constructor() {
+ }
+
+ public accept(color: string, startLineNumber: number, endLineNumber: number, lane: number): void {
+ let prev = this.result[color];
+
+ if (prev) {
+ const prevLane = prev[prev.length - 3];
+ const prevEndLineNumber = prev[prev.length - 1];
+ if (prevLane === lane && prevEndLineNumber + 1 >= startLineNumber) {
+ // merge into prev
+ if (endLineNumber > prevEndLineNumber) {
+ prev[prev.length - 1] = endLineNumber;
+ }
+ return;
+ }
+
+ // push
+ prev.push(lane, startLineNumber, endLineNumber);
+ } else {
+ this.result[color] = [lane, startLineNumber, endLineNumber];
+ }
+ }
+}
+
+
+function resolveColor(opts: ModelDecorationOverviewRulerOptions, theme: ITheme): string {
+ if (!opts._resolvedColor) {
+ const themeType = theme.type;
+ const color = (themeType === 'dark' ? opts.darkColor : themeType === 'light' ? opts.color : opts.hcColor);
+ opts._resolvedColor = resolveRulerColor(color, theme);
+ }
+ return opts._resolvedColor;
+}
+
+function resolveRulerColor(color: string | ThemeColor, theme: ITheme): string {
+ if (typeof color === 'string') {
+ return color;
+ }
+ let c = color ? theme.getColor(color.id) : null;
+ if (!c) {
+ c = Color.transparent;
+ }
+ return c.toString();
}
diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts
index 2890bf9314f88..88787418a97df 100644
--- a/src/vs/editor/common/viewModel/viewModel.ts
+++ b/src/vs/editor/common/viewModel/viewModel.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
-import { INewScrollPosition, IModelDecoration, EndOfLinePreference, IViewState } from 'vs/editor/common/editorCommon';
+import { INewScrollPosition, EndOfLinePreference, IViewState, IModelDecorationOptions } from 'vs/editor/common/editorCommon';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@@ -14,6 +14,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { Scrollable, IScrollPosition } from 'vs/base/common/scrollable';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
+import { ITheme } from 'vs/platform/theme/common/themeService';
export interface IViewWhitespaceViewportData {
readonly id: number;
@@ -138,7 +139,8 @@ export interface IViewModel {
getLineMaxColumn(lineNumber: number): number;
getLineFirstNonWhitespaceColumn(lineNumber: number): number;
getLineLastNonWhitespaceColumn(lineNumber: number): number;
- getAllOverviewRulerDecorations(): ViewModelDecoration[];
+ getAllOverviewRulerDecorations(theme: ITheme): IOverviewRulerDecorations;
+ invalidateOverviewRulerColorCache(): void;
getValueInRange(range: Range, eol: EndOfLinePreference): string;
getModelLineMaxColumn(modelLineNumber: number): number;
@@ -267,15 +269,25 @@ export class InlineDecoration {
export class ViewModelDecoration {
_viewModelDecorationBrand: void;
- public range: Range;
- public readonly source: IModelDecoration;
+ public readonly range: Range;
+ public readonly options: IModelDecorationOptions;
- constructor(source: IModelDecoration) {
- this.range = null;
- this.source = source;
+ constructor(range: Range, options: IModelDecorationOptions) {
+ this.range = range;
+ this.options = options;
}
}
+/**
+ * Decorations are encoded in a number array using the following scheme:
+ * - 3*i = lane
+ * - 3*i+1 = startLineNumber
+ * - 3*i+2 = endLineNumber
+ */
+export interface IOverviewRulerDecorations {
+ [color: string]: number[];
+}
+
export class ViewEventsCollector {
private _events: ViewEvent[];
diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts
index f4311681f8214..b5c6aa4b5ba01 100644
--- a/src/vs/editor/common/viewModel/viewModelDecorations.ts
+++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts
@@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { InlineDecoration, ViewModelDecoration, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
-import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents';
+import { IViewModelLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
export interface IDecorationsViewportData {
/**
@@ -27,6 +27,7 @@ export class ViewModelDecorations implements IDisposable {
private readonly editorId: number;
private readonly model: editorCommon.IModel;
private readonly configuration: editorCommon.IConfiguration;
+ private readonly _linesCollection: IViewModelLinesCollection;
private readonly _coordinatesConverter: ICoordinatesConverter;
private _decorationsCache: { [decorationId: string]: ViewModelDecoration; };
@@ -34,10 +35,11 @@ export class ViewModelDecorations implements IDisposable {
private _cachedModelDecorationsResolver: IDecorationsViewportData;
private _cachedModelDecorationsResolverViewRange: Range;
- constructor(editorId: number, model: editorCommon.IModel, configuration: editorCommon.IConfiguration, coordinatesConverter: ICoordinatesConverter) {
+ constructor(editorId: number, model: editorCommon.IModel, configuration: editorCommon.IConfiguration, linesCollection: IViewModelLinesCollection, coordinatesConverter: ICoordinatesConverter) {
this.editorId = editorId;
this.model = model;
this.configuration = configuration;
+ this._linesCollection = linesCollection;
this._coordinatesConverter = coordinatesConverter;
this._decorationsCache = Object.create(null);
this._clearCachedModelDecorationsResolver();
@@ -58,26 +60,8 @@ export class ViewModelDecorations implements IDisposable {
this._clearCachedModelDecorationsResolver();
}
- public onModelDecorationsChanged(e: IModelDecorationsChangedEvent): void {
- let changedDecorations = e.changedDecorations;
- for (let i = 0, len = changedDecorations.length; i < len; i++) {
- let changedDecoration = changedDecorations[i];
- let myDecoration = this._decorationsCache[changedDecoration];
- if (!myDecoration) {
- continue;
- }
-
- myDecoration.range = null;
- }
-
- let removedDecorations = e.removedDecorations;
- if (this._decorationsCache !== null && this._decorationsCache !== undefined) {
- for (let i = 0, len = removedDecorations.length; i < len; i++) {
- let removedDecoration = removedDecorations[i];
- delete this._decorationsCache[removedDecoration];
- }
- }
-
+ public onModelDecorationsChanged(): void {
+ this._decorationsCache = Object.create(null);
this._clearCachedModelDecorationsResolver();
}
@@ -88,42 +72,25 @@ export class ViewModelDecorations implements IDisposable {
}
private _getOrCreateViewModelDecoration(modelDecoration: editorCommon.IModelDecoration): ViewModelDecoration {
- let id = modelDecoration.id;
+ const id = modelDecoration.id;
let r = this._decorationsCache[id];
if (!r) {
- r = new ViewModelDecoration(modelDecoration);
- this._decorationsCache[id] = r;
- }
- if (r.range === null) {
const modelRange = modelDecoration.range;
- if (modelDecoration.options.isWholeLine) {
- let start = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.startLineNumber, 1));
- let end = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber)));
- r.range = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
+ const options = modelDecoration.options;
+ let viewRange: Range;
+ if (options.isWholeLine) {
+ const start = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.startLineNumber, 1));
+ const end = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber)));
+ viewRange = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
} else {
- r.range = this._coordinatesConverter.convertModelRangeToViewRange(modelRange);
+ viewRange = this._coordinatesConverter.convertModelRangeToViewRange(modelRange);
}
+ r = new ViewModelDecoration(viewRange, options);
+ this._decorationsCache[id] = r;
}
return r;
}
- public getAllOverviewRulerDecorations(): ViewModelDecoration[] {
- let modelDecorations = this.model.getAllDecorations(this.editorId, this.configuration.editor.readOnly);
- let result: ViewModelDecoration[] = [], resultLen = 0;
- for (let i = 0, len = modelDecorations.length; i < len; i++) {
- let modelDecoration = modelDecorations[i];
- let decorationOptions = modelDecoration.options;
-
- if (!decorationOptions.overviewRuler.color) {
- continue;
- }
-
- let viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration);
- result[resultLen++] = viewModelDecoration;
- }
- return result;
- }
-
public getDecorationsViewportData(viewRange: Range): IDecorationsViewportData {
var cacheIsValid = true;
cacheIsValid = cacheIsValid && (this._cachedModelDecorationsResolver !== null);
@@ -136,10 +103,9 @@ export class ViewModelDecorations implements IDisposable {
}
private _getDecorationsViewportData(viewportRange: Range): IDecorationsViewportData {
- let viewportModelRange = this._coordinatesConverter.convertViewRangeToModelRange(viewportRange);
- let startLineNumber = viewportRange.startLineNumber;
- let endLineNumber = viewportRange.endLineNumber;
- let modelDecorations = this.model.getDecorationsInRange(viewportModelRange, this.editorId, this.configuration.editor.readOnly);
+ const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, this.configuration.editor.readOnly);
+ const startLineNumber = viewportRange.startLineNumber;
+ const endLineNumber = viewportRange.endLineNumber;
let decorationsInViewport: ViewModelDecoration[] = [], decorationsInViewportLen = 0;
let inlineDecorations: InlineDecoration[][] = [];
diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts
index f1a7fcbf961b3..25a52e2f2b9e5 100644
--- a/src/vs/editor/common/viewModel/viewModelImpl.ts
+++ b/src/vs/editor/common/viewModel/viewModelImpl.ts
@@ -12,7 +12,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
import { TokenizationRegistry, ColorId, LanguageId } from 'vs/editor/common/modes';
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
-import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, ViewEventsCollector } from 'vs/editor/common/viewModel/viewModel';
+import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, ViewEventsCollector, IOverviewRulerDecorations } from 'vs/editor/common/viewModel/viewModel';
import { SplitLinesCollection, IViewModelLinesCollection, IdentityLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer';
@@ -22,6 +22,8 @@ import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewMod
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
import { Color } from 'vs/base/common/color';
import { IDisposable } from 'vs/base/common/lifecycle';
+import { ITheme } from 'vs/platform/theme/common/themeService';
+import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModelWithDecorations';
const USE_IDENTITY_LINES_COLLECTION = true;
@@ -80,7 +82,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this._isDisposing = false;
this._centeredViewLine = -1;
- this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.coordinatesConverter);
+ this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter);
this._register(this.model.addBulkListener((events: EmitterEvent[]) => {
if (this._isDisposing) {
@@ -282,8 +284,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
break;
}
case textModelEvents.TextModelEventType.ModelDecorationsChanged: {
- const e = data;
- this.decorations.onModelDecorationsChanged(e);
+ this.decorations.onModelDecorationsChanged();
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
break;
}
@@ -429,8 +430,17 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
);
}
- public getAllOverviewRulerDecorations(): ViewModelDecoration[] {
- return this.decorations.getAllOverviewRulerDecorations();
+ public getAllOverviewRulerDecorations(theme: ITheme): IOverviewRulerDecorations {
+ return this.lines.getAllOverviewRulerDecorations(this.editorId, this.configuration.editor.readOnly, theme);
+ }
+
+ public invalidateOverviewRulerColorCache(): void {
+ const decorations = this.model.getOverviewRulerDecorations();
+ for (let i = 0, len = decorations.length; i < len; i++) {
+ const decoration = decorations[i];
+ const opts = decoration.options.overviewRuler;
+ opts._resolvedColor = null;
+ }
}
public getValueInRange(range: Range, eol: editorCommon.EndOfLinePreference): string {
diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts
index 726c2451eeff8..7cb6a00b5f0a7 100644
--- a/src/vs/editor/contrib/find/browser/findWidget.ts
+++ b/src/vs/editor/contrib/find/browser/findWidget.ts
@@ -48,7 +48,7 @@ const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Repla
const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace");
const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All");
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode");
-const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first 999 results are highlighted, but all find operations work on the entire text.");
+const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first {0} results are highlighted, but all find operations work on the entire text.", MATCHES_LIMIT);
const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");
const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
diff --git a/src/vs/editor/contrib/find/common/findController.ts b/src/vs/editor/contrib/find/common/findController.ts
index 09536d1f5f82e..43ea3992e6854 100644
--- a/src/vs/editor/contrib/find/common/findController.ts
+++ b/src/vs/editor/contrib/find/common/findController.ts
@@ -1038,6 +1038,23 @@ export class SelectionHighlighter extends Disposable implements editorCommon.IEd
}
}
+ // Return early if the find widget shows the exact same matches
+ if (findState.isRevealed) {
+ let findStateSearchString = findState.searchString;
+ if (!caseSensitive) {
+ findStateSearchString = findStateSearchString.toLowerCase();
+ }
+
+ let mySearchString = r.searchText;
+ if (!caseSensitive) {
+ mySearchString = mySearchString.toLowerCase();
+ }
+
+ if (findStateSearchString === mySearchString && r.matchCase === findState.matchCase && r.wholeWord === findState.wholeWord && !findState.isRegex) {
+ return null;
+ }
+ }
+
return new SelectionHighlighterState(lastWordUnderCursor, r.searchText, r.matchCase, r.wholeWord ? editor.getConfiguration().wordSeparators : null);
}
diff --git a/src/vs/editor/contrib/find/common/findDecorations.ts b/src/vs/editor/contrib/find/common/findDecorations.ts
index 202e32602faca..09cb7eb07d06f 100644
--- a/src/vs/editor/contrib/find/common/findDecorations.ts
+++ b/src/vs/editor/contrib/find/common/findDecorations.ts
@@ -16,6 +16,7 @@ export class FindDecorations implements IDisposable {
private _editor: editorCommon.ICommonCodeEditor;
private _decorations: string[];
+ private _overviewRulerApproximateDecorations: string[];
private _findScopeDecorationId: string;
private _rangeHighlightDecorationId: string;
private _highlightedDecorationId: string;
@@ -24,6 +25,7 @@ export class FindDecorations implements IDisposable {
constructor(editor: editorCommon.ICommonCodeEditor) {
this._editor = editor;
this._decorations = [];
+ this._overviewRulerApproximateDecorations = [];
this._findScopeDecorationId = null;
this._rangeHighlightDecorationId = null;
this._highlightedDecorationId = null;
@@ -35,6 +37,7 @@ export class FindDecorations implements IDisposable {
this._editor = null;
this._decorations = [];
+ this._overviewRulerApproximateDecorations = [];
this._findScopeDecorationId = null;
this._rangeHighlightDecorationId = null;
this._highlightedDecorationId = null;
@@ -43,6 +46,7 @@ export class FindDecorations implements IDisposable {
public reset(): void {
this._decorations = [];
+ this._overviewRulerApproximateDecorations = [];
this._findScopeDecorationId = null;
this._rangeHighlightDecorationId = null;
this._highlightedDecorationId = null;
@@ -68,11 +72,21 @@ export class FindDecorations implements IDisposable {
this.setCurrentFindMatch(null);
}
+ private _getDecorationIndex(decorationId: string): number {
+ const index = this._decorations.indexOf(decorationId);
+ if (index >= 0) {
+ return index + 1;
+ }
+ return 1;
+ }
+
public getCurrentMatchesPosition(desiredRange: Range): number {
- for (let i = 0, len = this._decorations.length; i < len; i++) {
- let range = this._editor.getModel().getDecorationRange(this._decorations[i]);
- if (desiredRange.equalsRange(range)) {
- return (i + 1);
+ let candidates = this._editor.getModel().getDecorationsInRange(desiredRange);
+ for (let i = 0, len = candidates.length; i < len; i++) {
+ const candidate = candidates[i];
+ const candidateOpts = candidate.options;
+ if (candidateOpts === FindDecorations._FIND_MATCH_DECORATION || candidateOpts === FindDecorations._CURRENT_FIND_MATCH_DECORATION) {
+ return this._getDecorationIndex(candidate.id);
}
}
return 1;
@@ -95,12 +109,12 @@ export class FindDecorations implements IDisposable {
if (this._highlightedDecorationId !== null || newCurrentDecorationId !== null) {
this._editor.changeDecorations((changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => {
if (this._highlightedDecorationId !== null) {
- changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations.createFindMatchDecorationOptions(false));
+ changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations._FIND_MATCH_DECORATION);
this._highlightedDecorationId = null;
}
if (newCurrentDecorationId !== null) {
this._highlightedDecorationId = newCurrentDecorationId;
- changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations.createFindMatchDecorationOptions(true));
+ changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations._CURRENT_FIND_MATCH_DECORATION);
}
if (this._rangeHighlightDecorationId !== null) {
changeAccessor.removeDecoration(this._rangeHighlightDecorationId);
@@ -121,34 +135,82 @@ export class FindDecorations implements IDisposable {
return matchPosition;
}
- public set(matches: Range[], findScope: Range): void {
- let newDecorations: editorCommon.IModelDeltaDecoration[] = matches.map((match) => {
- return {
- range: match,
- options: FindDecorations.createFindMatchDecorationOptions(false)
- };
- });
- if (findScope) {
- newDecorations.unshift({
- range: findScope,
- options: FindDecorations._FIND_SCOPE_DECORATION
- });
- }
- let tmpDecorations = this._editor.deltaDecorations(this._allDecorations(), newDecorations);
+ public set(findMatches: editorCommon.FindMatch[], findScope: Range): void {
+ this._editor.changeDecorations((accessor) => {
- if (findScope) {
- this._findScopeDecorationId = tmpDecorations.shift();
- } else {
- this._findScopeDecorationId = null;
- }
- this._decorations = tmpDecorations;
- this._rangeHighlightDecorationId = null;
- this._highlightedDecorationId = null;
+ let findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION;
+ let newOverviewRulerApproximateDecorations: editorCommon.IModelDeltaDecoration[] = [];
+
+ if (findMatches.length > 1000) {
+ // we go into a mode where the overview ruler gets "approximate" decorations
+ // the reason is that the overview ruler paints all the decorations in the file and we don't want to cause freezes
+ findMatchesOptions = FindDecorations._FIND_MATCH_NO_OVERVIEW_DECORATION;
+
+ // approximate a distance in lines where matches should be merged
+ const lineCount = this._editor.getModel().getLineCount();
+ const height = this._editor.getLayoutInfo().height;
+ const approxPixelsPerLine = height / lineCount;
+ const mergeLinesDelta = Math.max(2, Math.ceil(3 / approxPixelsPerLine));
+
+ // merge decorations as much as possible
+ let prevStartLineNumber = findMatches[0].range.startLineNumber;
+ let prevEndLineNumber = findMatches[0].range.endLineNumber;
+ for (let i = 1, len = findMatches.length; i < len; i++) {
+ const range = findMatches[i].range;
+ if (prevEndLineNumber + mergeLinesDelta >= range.startLineNumber) {
+ if (range.endLineNumber > prevEndLineNumber) {
+ prevEndLineNumber = range.endLineNumber;
+ }
+ } else {
+ newOverviewRulerApproximateDecorations.push({
+ range: new Range(prevStartLineNumber, 1, prevEndLineNumber, 1),
+ options: FindDecorations._FIND_MATCH_ONLY_OVERVIEW_DECORATION
+ });
+ prevStartLineNumber = range.startLineNumber;
+ prevEndLineNumber = range.endLineNumber;
+ }
+ }
+
+ newOverviewRulerApproximateDecorations.push({
+ range: new Range(prevStartLineNumber, 1, prevEndLineNumber, 1),
+ options: FindDecorations._FIND_MATCH_ONLY_OVERVIEW_DECORATION
+ });
+ }
+
+ // Find matches
+ let newFindMatchesDecorations: editorCommon.IModelDeltaDecoration[] = new Array(findMatches.length);
+ for (let i = 0, len = findMatches.length; i < len; i++) {
+ newFindMatchesDecorations[i] = {
+ range: findMatches[i].range,
+ options: findMatchesOptions
+ };
+ }
+ this._decorations = accessor.deltaDecorations(this._decorations, newFindMatchesDecorations);
+
+ // Overview ruler approximate decorations
+ this._overviewRulerApproximateDecorations = accessor.deltaDecorations(this._overviewRulerApproximateDecorations, newOverviewRulerApproximateDecorations);
+
+ // Range highlight
+ if (this._rangeHighlightDecorationId) {
+ accessor.removeDecoration(this._rangeHighlightDecorationId);
+ this._rangeHighlightDecorationId = null;
+ }
+
+ // Find scope
+ if (this._findScopeDecorationId) {
+ accessor.removeDecoration(this._findScopeDecorationId);
+ this._findScopeDecorationId = null;
+ }
+ if (findScope) {
+ this._findScopeDecorationId = accessor.addDecoration(findScope, FindDecorations._FIND_SCOPE_DECORATION);
+ }
+ });
}
private _allDecorations(): string[] {
let result: string[] = [];
result = result.concat(this._decorations);
+ result = result.concat(this._overviewRulerApproximateDecorations);
if (this._findScopeDecorationId) {
result.push(this._findScopeDecorationId);
}
@@ -158,10 +220,6 @@ export class FindDecorations implements IDisposable {
return result;
}
- private static createFindMatchDecorationOptions(isCurrent: boolean): ModelDecorationOptions {
- return (isCurrent ? this._CURRENT_FIND_MATCH_DECORATION : this._FIND_MATCH_DECORATION);
- }
-
private static _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'currentFindMatch',
@@ -184,6 +242,21 @@ export class FindDecorations implements IDisposable {
}
});
+ private static _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({
+ stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
+ className: 'findMatch',
+ showIfCollapsed: true
+ });
+
+ private static _FIND_MATCH_ONLY_OVERVIEW_DECORATION = ModelDecorationOptions.register({
+ stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
+ overviewRuler: {
+ color: themeColorFromId(editorFindMatchHighlight),
+ darkColor: themeColorFromId(editorFindMatchHighlight),
+ position: editorCommon.OverviewRulerLane.Center
+ }
+ });
+
private static _RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'rangeHighlight',
diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts
index be3705b47f72e..dda6ca2f533bd 100644
--- a/src/vs/editor/contrib/find/common/findModel.ts
+++ b/src/vs/editor/contrib/find/common/findModel.ts
@@ -67,7 +67,7 @@ export const FIND_IDS = {
ShowNextFindTermAction: 'find.history.showNext'
};
-export const MATCHES_LIMIT = 999;
+export const MATCHES_LIMIT = 19999;
export class FindModelBoundToEditorModel {
@@ -171,7 +171,7 @@ export class FindModelBoundToEditorModel {
}
let findMatches = this._findMatches(findScope, false, MATCHES_LIMIT);
- this._decorations.set(findMatches.map(match => match.range), findScope);
+ this._decorations.set(findMatches, findScope);
this._state.changeMatchInfo(
this._decorations.getCurrentMatchesPosition(this._editor.getSelection()),
diff --git a/src/vs/editor/contrib/linesOperations/test/common/sortLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/common/sortLinesCommand.test.ts
index ec19e7bbe0dc5..58aa224ff6cd9 100644
--- a/src/vs/editor/contrib/linesOperations/test/common/sortLinesCommand.test.ts
+++ b/src/vs/editor/contrib/linesOperations/test/common/sortLinesCommand.test.ts
@@ -77,7 +77,7 @@ suite('Editor Contrib - Sort Lines Command', () => {
'third line',
'fifth'
],
- new Selection(3, 3, 4, 2)
+ new Selection(3, 3, 4, 1)
);
});
@@ -119,7 +119,7 @@ suite('Editor Contrib - Sort Lines Command', () => {
'second line',
'third line',
],
- new Selection(1, 1, 5, 6)
+ new Selection(1, 1, 5, 11)
);
});
diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts
index 0d299ca85af65..ce7638670eb62 100644
--- a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts
+++ b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts
@@ -38,7 +38,6 @@ import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/r
import { registerColor, activeContrastBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant, ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler, attachBadgeStyler } from 'vs/platform/theme/common/styler';
-import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
@@ -81,7 +80,7 @@ class DecorationsManager implements IDisposable {
}
private _addDecorations(reference: FileReferences): void {
- this._callOnModelChange.push(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged(event)));
+ this._callOnModelChange.push(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged()));
this._editor.changeDecorations(accessor => {
@@ -107,21 +106,20 @@ class DecorationsManager implements IDisposable {
});
}
- private _onDecorationChanged(event: IModelDecorationsChangedEvent): void {
- const changedDecorations = event.changedDecorations,
- toRemove: string[] = [];
+ private _onDecorationChanged(): void {
+ const toRemove: string[] = [];
- for (let i = 0, len = changedDecorations.length; i < len; i++) {
- let reference = this._decorations.get(changedDecorations[i]);
- if (!reference) {
- continue;
+ this._decorations.forEach((reference, decorationId) => {
+ const newRange = this._editor.getModel().getDecorationRange(decorationId);
+
+ if (!newRange) {
+ return;
}
- const newRange = this._editor.getModel().getDecorationRange(changedDecorations[i]);
let ignore = false;
if (Range.equalsRange(newRange, reference.range)) {
- continue;
+ return;
} else if (Range.spansMultipleLines(newRange)) {
ignore = true;
@@ -137,11 +135,11 @@ class DecorationsManager implements IDisposable {
if (ignore) {
this._decorationIgnoreSet.add(reference.id);
- toRemove.push(changedDecorations[i]);
+ toRemove.push(decorationId);
} else {
reference.range = newRange;
}
- }
+ });
this._editor.changeDecorations((accessor) => {
for (let i = 0, len = toRemove.length; i < len; i++) {
diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts
index 2bb0c11006dff..05d96ea0b304e 100644
--- a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts
+++ b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts
@@ -234,9 +234,9 @@ suite('SnippetSession', function () {
assertSelections(editor, new Selection(1, 10, 1, 10), new Selection(2, 14, 2, 14));
session.prev();
- assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
+ assertSelections(editor, new Selection(1, 7, 1, 10), new Selection(2, 11, 2, 14));
session.prev();
- assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8));
+ assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
session.prev();
assertSelections(editor, new Selection(1, 1, 1, 4), new Selection(2, 5, 2, 8));
});
diff --git a/src/vs/editor/test/common/commands/sideEditing.test.ts b/src/vs/editor/test/common/commands/sideEditing.test.ts
index 08fe4e0c5e747..ceaa7df8265a5 100644
--- a/src/vs/editor/test/common/commands/sideEditing.test.ts
+++ b/src/vs/editor/test/common/commands/sideEditing.test.ts
@@ -10,10 +10,11 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon';
-import { ILineEdit, ModelLine, LineMarker, MarkersTracker } from 'vs/editor/common/model/modelLine';
import { withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
-
-const NO_TAB_SIZE = 0;
+import { Model } from 'vs/editor/common/model/model';
+import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
+import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
+import { Cursor } from 'vs/editor/common/controller/cursor';
function testCommand(lines: string[], selections: Selection[], edits: IIdentifiedSingleEditOperation[], expectedLines: string[], expectedSelections: Selection[]): void {
withMockCodeEditor(lines, {}, (editor, cursor) => {
@@ -31,15 +32,6 @@ function testCommand(lines: string[], selections: Selection[], edits: IIdentifie
});
}
-function testLineEditMarker(text: string, column: number, stickToPreviousCharacter: boolean, edit: ILineEdit, expectedColumn: number): void {
- var line = new ModelLine(text, NO_TAB_SIZE);
- line.addMarker(new LineMarker('1', 0, new Position(0, column), stickToPreviousCharacter));
-
- line.applyEdits(new MarkersTracker(), [edit], NO_TAB_SIZE);
-
- assert.equal(line.getMarkers()[0].position.column, expectedColumn);
-}
-
suite('Editor Side Editing - collapsed selection', () => {
test('replace at selection', () => {
@@ -86,14 +78,6 @@ suite('Editor Side Editing - collapsed selection', () => {
);
});
- test('ModelLine.applyEdits uses `isReplace`', () => {
- testLineEditMarker('something', 1, true, { startColumn: 1, endColumn: 1, text: 'asd', forceMoveMarkers: false }, 1);
- testLineEditMarker('something', 1, true, { startColumn: 1, endColumn: 1, text: 'asd', forceMoveMarkers: true }, 4);
-
- testLineEditMarker('something', 1, false, { startColumn: 1, endColumn: 1, text: 'asd', forceMoveMarkers: false }, 4);
- testLineEditMarker('something', 1, false, { startColumn: 1, endColumn: 1, text: 'asd', forceMoveMarkers: true }, 4);
- });
-
test('insert at selection', () => {
testCommand(
[
@@ -204,5 +188,699 @@ suite('Editor Side Editing - collapsed selection', () => {
[new Selection(1, 1, 1, 1), new Selection(1, 3, 1, 3)]
);
});
+});
+
+suite('SideEditing', () => {
+
+ const LINES = [
+ 'My First Line',
+ 'My Second Line',
+ 'Third Line'
+ ];
+
+ function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void {
+ const model = Model.createFromString(LINES.join('\n'));
+ const config = new TestConfiguration(null);
+ const viewModel = new ViewModel(0, config, model, null);
+ const cursor = new Cursor(config, model, viewModel);
+
+ cursor.setSelections('tests', [selection]);
+ model.applyEdits([{ range: editRange, text: editText, forceMoveMarkers: editForceMoveMarkers, identifier: null }]);
+ const actual = cursor.getSelection();
+ assert.deepEqual(actual.toString(), expected.toString(), msg);
+
+ cursor.dispose();
+ viewModel.dispose();
+ config.dispose();
+ model.dispose();
+ }
+
+ function runTest(selection: Range, editRange: Range, editText: string, expected: Selection[][]): void {
+ const sel1 = new Selection(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn);
+ _runTest(sel1, editRange, editText, false, expected[0][0], '0-0-regular-no-force');
+ _runTest(sel1, editRange, editText, true, expected[1][0], '1-0-regular-force');
+
+ // RTL selection
+ const sel2 = new Selection(selection.endLineNumber, selection.endColumn, selection.startLineNumber, selection.startColumn);
+ _runTest(sel2, editRange, editText, false, expected[0][1], '0-1-inverse-no-force');
+ _runTest(sel2, editRange, editText, true, expected[1][1], '1-1-inverse-force');
+ }
+
+ suite('insert', () => {
+ suite('collapsed sel', () => {
+ test('before', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 3), 'xx',
+ [
+ [new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
+ [new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('equal', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 4), 'xx',
+ [
+ [new Selection(1, 4, 1, 6), new Selection(1, 4, 1, 6)],
+ [new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('after', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 5), 'xx',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('before', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 3), 'xx',
+ [
+ [new Selection(1, 6, 1, 11), new Selection(1, 11, 1, 6)],
+ [new Selection(1, 6, 1, 11), new Selection(1, 11, 1, 6)],
+ ]
+ );
+ });
+ test('start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 4), 'xx',
+ [
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ [new Selection(1, 6, 1, 11), new Selection(1, 11, 1, 6)],
+ ]
+ );
+ });
+ test('inside', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 5), 'xx',
+ [
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ ]
+ );
+ });
+ test('end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 9), 'xx',
+ [
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ ]
+ );
+ });
+ test('after', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 10), 'xx',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ ]
+ );
+ });
+ });
+ });
+
+ suite('delete', () => {
+ suite('collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 1, 1, 3), '',
+ [
+ [new Selection(1, 2, 1, 2), new Selection(1, 2, 1, 2)],
+ [new Selection(1, 2, 1, 2), new Selection(1, 2, 1, 2)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 2, 1, 4), '',
+ [
+ [new Selection(1, 2, 1, 2), new Selection(1, 2, 1, 2)],
+ [new Selection(1, 2, 1, 2), new Selection(1, 2, 1, 2)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 5), '',
+ [
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ ]
+ );
+ });
+ test('edit.start >= range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 6), '',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 7), '',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 1, 1, 3), '',
+ [
+ [new Selection(1, 2, 1, 7), new Selection(1, 7, 1, 2)],
+ [new Selection(1, 2, 1, 7), new Selection(1, 7, 1, 2)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 2, 1, 4), '',
+ [
+ [new Selection(1, 2, 1, 7), new Selection(1, 7, 1, 2)],
+ [new Selection(1, 2, 1, 7), new Selection(1, 7, 1, 2)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 5), '',
+ [
+ [new Selection(1, 3, 1, 7), new Selection(1, 7, 1, 3)],
+ [new Selection(1, 3, 1, 7), new Selection(1, 7, 1, 3)],
+ ]
+ );
+ });
+
+ test('edit.start < range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 9), '',
+ [
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ ]
+ );
+ });
+
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 10), '',
+ [
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ ]
+ );
+ });
+
+ test('edit.start == range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 6), '',
+ [
+ [new Selection(1, 4, 1, 7), new Selection(1, 7, 1, 4)],
+ [new Selection(1, 4, 1, 7), new Selection(1, 7, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start == range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 9), '',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start == range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 10), '',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start > range.start && edit.start < range.end && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 7), '',
+ [
+ [new Selection(1, 4, 1, 7), new Selection(1, 7, 1, 4)],
+ [new Selection(1, 4, 1, 7), new Selection(1, 7, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start > range.start && edit.start < range.end && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 9), '',
+ [
+ [new Selection(1, 4, 1, 5), new Selection(1, 5, 1, 4)],
+ [new Selection(1, 4, 1, 5), new Selection(1, 5, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start > range.start && edit.start < range.end && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 10), '',
+ [
+ [new Selection(1, 4, 1, 5), new Selection(1, 5, 1, 4)],
+ [new Selection(1, 4, 1, 5), new Selection(1, 5, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 11), '',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 11), '',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ ]
+ );
+ });
+ });
+ });
+
+ suite('replace short', () => {
+ suite('collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 1, 1, 3), 'c',
+ [
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 2, 1, 4), 'c',
+ [
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ [new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 5), 'c',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start >= range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 6), 'c',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 5, 1, 5), new Selection(1, 5, 1, 5)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 7), 'c',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 1, 1, 3), 'c',
+ [
+ [new Selection(1, 3, 1, 8), new Selection(1, 8, 1, 3)],
+ [new Selection(1, 3, 1, 8), new Selection(1, 8, 1, 3)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 2, 1, 4), 'c',
+ [
+ [new Selection(1, 3, 1, 8), new Selection(1, 8, 1, 3)],
+ [new Selection(1, 3, 1, 8), new Selection(1, 8, 1, 3)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 5), 'c',
+ [
+ [new Selection(1, 4, 1, 8), new Selection(1, 8, 1, 4)],
+ [new Selection(1, 4, 1, 8), new Selection(1, 8, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 9), 'c',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 10), 'c',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 6), 'c',
+ [
+ [new Selection(1, 4, 1, 8), new Selection(1, 8, 1, 4)],
+ [new Selection(1, 5, 1, 8), new Selection(1, 8, 1, 5)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 9), 'c',
+ [
+ [new Selection(1, 4, 1, 5), new Selection(1, 5, 1, 4)],
+ [new Selection(1, 5, 1, 5), new Selection(1, 5, 1, 5)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 10), 'c',
+ [
+ [new Selection(1, 4, 1, 5), new Selection(1, 5, 1, 4)],
+ [new Selection(1, 5, 1, 5), new Selection(1, 5, 1, 5)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 7), 'c',
+ [
+ [new Selection(1, 4, 1, 8), new Selection(1, 8, 1, 4)],
+ [new Selection(1, 4, 1, 8), new Selection(1, 8, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 9), 'c',
+ [
+ [new Selection(1, 4, 1, 6), new Selection(1, 6, 1, 4)],
+ [new Selection(1, 4, 1, 6), new Selection(1, 6, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 10), 'c',
+ [
+ [new Selection(1, 4, 1, 6), new Selection(1, 6, 1, 4)],
+ [new Selection(1, 4, 1, 6), new Selection(1, 6, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 11), 'c',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 10), new Selection(1, 10, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 11), 'c',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ ]
+ );
+ });
+ });
+ });
+ suite('replace long', () => {
+ suite('collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 1, 1, 3), 'cccc',
+ [
+ [new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
+ [new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 2, 1, 4), 'cccc',
+ [
+ [new Selection(1, 4, 1, 6), new Selection(1, 4, 1, 6)],
+ [new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 5), 'cccc',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 7, 1, 7), new Selection(1, 7, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start >= range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 6), 'cccc',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 8, 1, 8), new Selection(1, 8, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 7), 'cccc',
+ [
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ [new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 1, 1, 3), 'cccc',
+ [
+ [new Selection(1, 6, 1, 11), new Selection(1, 11, 1, 6)],
+ [new Selection(1, 6, 1, 11), new Selection(1, 11, 1, 6)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 2, 1, 4), 'cccc',
+ [
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ [new Selection(1, 6, 1, 11), new Selection(1, 11, 1, 6)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 5), 'cccc',
+ [
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ [new Selection(1, 7, 1, 11), new Selection(1, 11, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 9), 'cccc',
+ [
+ [new Selection(1, 4, 1, 7), new Selection(1, 7, 1, 4)],
+ [new Selection(1, 7, 1, 7), new Selection(1, 7, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 10), 'cccc',
+ [
+ [new Selection(1, 4, 1, 7), new Selection(1, 7, 1, 4)],
+ [new Selection(1, 7, 1, 7), new Selection(1, 7, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 6), 'cccc',
+ [
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ [new Selection(1, 8, 1, 11), new Selection(1, 11, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 9), 'cccc',
+ [
+ [new Selection(1, 4, 1, 8), new Selection(1, 8, 1, 4)],
+ [new Selection(1, 8, 1, 8), new Selection(1, 8, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 10), 'cccc',
+ [
+ [new Selection(1, 4, 1, 8), new Selection(1, 8, 1, 4)],
+ [new Selection(1, 8, 1, 8), new Selection(1, 8, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 7), 'cccc',
+ [
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ [new Selection(1, 4, 1, 11), new Selection(1, 11, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 9), 'cccc',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 10), 'cccc',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 11), 'cccc',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 13), new Selection(1, 13, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 11), 'cccc',
+ [
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ [new Selection(1, 4, 1, 9), new Selection(1, 9, 1, 4)],
+ ]
+ );
+ });
+ });
+ });
});
\ No newline at end of file
diff --git a/src/vs/editor/test/common/controller/cursor.test.ts b/src/vs/editor/test/common/controller/cursor.test.ts
index e21b52a667a5a..8b8a9bd46c46a 100644
--- a/src/vs/editor/test/common/controller/cursor.test.ts
+++ b/src/vs/editor/test/common/controller/cursor.test.ts
@@ -1651,7 +1651,8 @@ suite('Editor Controller - Regression tests', () => {
});
});
- test('issue #23913: Greater than 1000+ multi cursor typing replacement text appears inverted, lines begin to drop off selection', () => {
+ test('issue #23913: Greater than 1000+ multi cursor typing replacement text appears inverted, lines begin to drop off selection', function () {
+ this.timeout(10000);
const LINE_CNT = 2000;
let text = [];
diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts
index 6166f3eb8e568..d04e5269cadea 100644
--- a/src/vs/editor/test/common/model/editableTextModel.test.ts
+++ b/src/vs/editor/test/common/model/editableTextModel.test.ts
@@ -7,7 +7,7 @@
import * as assert from 'assert';
import { Range } from 'vs/editor/common/core/range';
-import { EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon';
+import { EndOfLineSequence, IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon';
import { EditableTextModel, IValidatedEditOperation } from 'vs/editor/common/model/editableTextModel';
import { MirrorModel } from 'vs/editor/common/model/mirrorModel';
import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils';
@@ -15,12 +15,13 @@ import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvent
suite('EditorModel - EditableTextModel._getInverseEdits', () => {
- function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string[]): IValidatedEditOperation {
+ function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): IValidatedEditOperation {
return {
sortIndex: 0,
identifier: null,
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
- rangeLength: rangeLength,
+ rangeOffset: 0,
+ rangeLength: 0,
lines: text,
forceMoveMarkers: false,
isAutoWhitespaceEdit: false
@@ -39,7 +40,7 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('single insert', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 1, 0, ['hello'])
+ editOp(1, 1, 1, 1, ['hello'])
],
[
inverseEditOp(1, 1, 1, 6)
@@ -50,8 +51,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('Bug 19872: Undo is funky', () => {
assertInverseEdits(
[
- editOp(2, 1, 2, 2, 0, ['']),
- editOp(3, 1, 4, 2, 0, [''])
+ editOp(2, 1, 2, 2, ['']),
+ editOp(3, 1, 4, 2, [''])
],
[
inverseEditOp(2, 1, 2, 1),
@@ -63,8 +64,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two single unrelated inserts', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 1, 0, ['hello']),
- editOp(2, 1, 2, 1, 0, ['world'])
+ editOp(1, 1, 1, 1, ['hello']),
+ editOp(2, 1, 2, 1, ['world'])
],
[
inverseEditOp(1, 1, 1, 6),
@@ -76,8 +77,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two single inserts 1', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 1, 0, ['hello']),
- editOp(1, 2, 1, 2, 0, ['world'])
+ editOp(1, 1, 1, 1, ['hello']),
+ editOp(1, 2, 1, 2, ['world'])
],
[
inverseEditOp(1, 1, 1, 6),
@@ -89,8 +90,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two single inserts 2', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 1, 0, ['hello']),
- editOp(1, 4, 1, 4, 0, ['world'])
+ editOp(1, 1, 1, 1, ['hello']),
+ editOp(1, 4, 1, 4, ['world'])
],
[
inverseEditOp(1, 1, 1, 6),
@@ -102,7 +103,7 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('multiline insert', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 1, 0, ['hello', 'world'])
+ editOp(1, 1, 1, 1, ['hello', 'world'])
],
[
inverseEditOp(1, 1, 2, 6)
@@ -113,8 +114,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two unrelated multiline inserts', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 1, 0, ['hello', 'world']),
- editOp(2, 1, 2, 1, 0, ['how', 'are', 'you?']),
+ editOp(1, 1, 1, 1, ['hello', 'world']),
+ editOp(2, 1, 2, 1, ['how', 'are', 'you?']),
],
[
inverseEditOp(1, 1, 2, 6),
@@ -126,8 +127,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two multiline inserts 1', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 1, 0, ['hello', 'world']),
- editOp(1, 2, 1, 2, 0, ['how', 'are', 'you?']),
+ editOp(1, 1, 1, 1, ['hello', 'world']),
+ editOp(1, 2, 1, 2, ['how', 'are', 'you?']),
],
[
inverseEditOp(1, 1, 2, 6),
@@ -139,7 +140,7 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('single delete', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 6, 0, null)
+ editOp(1, 1, 1, 6, null)
],
[
inverseEditOp(1, 1, 1, 1)
@@ -150,8 +151,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two single unrelated deletes', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 6, 0, null),
- editOp(2, 1, 2, 6, 0, null)
+ editOp(1, 1, 1, 6, null),
+ editOp(2, 1, 2, 6, null)
],
[
inverseEditOp(1, 1, 1, 1),
@@ -163,8 +164,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two single deletes 1', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 6, 0, null),
- editOp(1, 7, 1, 12, 0, null)
+ editOp(1, 1, 1, 6, null),
+ editOp(1, 7, 1, 12, null)
],
[
inverseEditOp(1, 1, 1, 1),
@@ -176,8 +177,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two single deletes 2', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 6, 0, null),
- editOp(1, 9, 1, 14, 0, null)
+ editOp(1, 1, 1, 6, null),
+ editOp(1, 9, 1, 14, null)
],
[
inverseEditOp(1, 1, 1, 1),
@@ -189,7 +190,7 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('multiline delete', () => {
assertInverseEdits(
[
- editOp(1, 1, 2, 6, 0, null)
+ editOp(1, 1, 2, 6, null)
],
[
inverseEditOp(1, 1, 1, 1)
@@ -200,8 +201,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two unrelated multiline deletes', () => {
assertInverseEdits(
[
- editOp(1, 1, 2, 6, 0, null),
- editOp(3, 1, 5, 5, 0, null),
+ editOp(1, 1, 2, 6, null),
+ editOp(3, 1, 5, 5, null),
],
[
inverseEditOp(1, 1, 1, 1),
@@ -213,8 +214,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two multiline deletes 1', () => {
assertInverseEdits(
[
- editOp(1, 1, 2, 6, 0, null),
- editOp(2, 7, 4, 5, 0, null),
+ editOp(1, 1, 2, 6, null),
+ editOp(2, 7, 4, 5, null),
],
[
inverseEditOp(1, 1, 1, 1),
@@ -226,7 +227,7 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('single replace', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 6, 0, ['Hello world'])
+ editOp(1, 1, 1, 6, ['Hello world'])
],
[
inverseEditOp(1, 1, 1, 12)
@@ -237,8 +238,8 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('two replaces', () => {
assertInverseEdits(
[
- editOp(1, 1, 1, 6, 0, ['Hello world']),
- editOp(1, 7, 1, 8, 0, ['How are you?']),
+ editOp(1, 1, 1, 6, ['Hello world']),
+ editOp(1, 7, 1, 8, ['How are you?']),
],
[
inverseEditOp(1, 1, 1, 12),
@@ -250,9 +251,9 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
test('many edits', () => {
assertInverseEdits(
[
- editOp(1, 2, 1, 2, 0, ['', ' ']),
- editOp(1, 5, 1, 6, 0, ['']),
- editOp(1, 9, 1, 9, 0, ['', ''])
+ editOp(1, 2, 1, 2, ['', ' ']),
+ editOp(1, 5, 1, 6, ['']),
+ editOp(1, 9, 1, 9, ['', ''])
],
[
inverseEditOp(1, 2, 2, 3),
@@ -265,11 +266,12 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => {
suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
- function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string[]): IValidatedEditOperation {
+ function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeOffset: number, rangeLength: number, text: string[]): IValidatedEditOperation {
return {
sortIndex: 0,
identifier: null,
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
+ rangeOffset: rangeOffset,
rangeLength: rangeLength,
lines: text,
forceMoveMarkers: false,
@@ -297,9 +299,9 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'1'
],
[
- editOp(1, 3, 1, 3, 0, [' new line', 'No longer'])
+ editOp(1, 3, 1, 3, 2, 0, [' new line', 'No longer'])
],
- editOp(1, 3, 1, 3, 0, [' new line', 'No longer'])
+ editOp(1, 3, 1, 3, 2, 0, [' new line', 'No longer'])
);
});
@@ -311,11 +313,11 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'',
'1'
], [
- editOp(1, 1, 1, 3, 0, ['Your']),
- editOp(1, 4, 1, 4, 0, ['Interesting ']),
- editOp(2, 3, 2, 6, 0, null)
+ editOp(1, 1, 1, 3, 0, 2, ['Your']),
+ editOp(1, 4, 1, 4, 3, 0, ['Interesting ']),
+ editOp(2, 3, 2, 6, 16, 3, null)
],
- editOp(1, 1, 2, 6, 19, [
+ editOp(1, 1, 2, 6, 0, 19, [
'Your Interesting First Line',
'\t\t'
]));
@@ -331,10 +333,10 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'1'
],
[
- editOp(1, 3, 1, 3, 0, ['', '', '', '', '']),
- editOp(3, 15, 3, 15, 0, ['a', 'b'])
+ editOp(1, 3, 1, 3, 2, 0, ['', '', '', '', '']),
+ editOp(3, 15, 3, 15, 45, 0, ['a', 'b'])
],
- editOp(1, 3, 3, 15, 43, [
+ editOp(1, 3, 3, 15, 2, 43, [
'',
'',
'',
@@ -357,9 +359,9 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'1'
],
[
- editOp(1, 1, 1, 1, 0, [''])
+ editOp(1, 1, 1, 1, 0, 0, [''])
],
- editOp(1, 1, 1, 1, 0, [''])
+ editOp(1, 1, 1, 1, 0, 0, [''])
);
});
@@ -373,10 +375,10 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'123'
],
[
- editOp(2, 1, 2, 3, 0, ['\t']),
- editOp(3, 1, 3, 5, 0, [''])
+ editOp(2, 1, 2, 3, 14, 2, ['\t']),
+ editOp(3, 1, 3, 5, 31, 4, [''])
],
- editOp(2, 1, 3, 5, 21, ['\tMy Second Line', ''])
+ editOp(2, 1, 3, 5, 14, 21, ['\tMy Second Line', ''])
);
});
@@ -386,11 +388,11 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'{"x" : 1}'
],
[
- editOp(1, 2, 1, 2, 0, ['\n ']),
- editOp(1, 5, 1, 6, 0, ['']),
- editOp(1, 9, 1, 9, 0, ['\n'])
+ editOp(1, 2, 1, 2, 1, 0, ['\n ']),
+ editOp(1, 5, 1, 6, 4, 1, ['']),
+ editOp(1, 9, 1, 9, 8, 0, ['\n'])
],
- editOp(1, 2, 1, 9, 7, [
+ editOp(1, 2, 1, 9, 1, 7, [
'',
' "x": 1',
''
@@ -406,11 +408,11 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'}'
],
[
- editOp(1, 2, 2, 3, 0, ['']),
- editOp(2, 6, 2, 6, 0, [' ']),
- editOp(2, 9, 3, 1, 0, [''])
+ editOp(1, 2, 2, 3, 1, 3, ['']),
+ editOp(2, 6, 2, 6, 7, 0, [' ']),
+ editOp(2, 9, 3, 1, 10, 1, [''])
],
- editOp(1, 2, 3, 1, 10, ['"x" : 1'])
+ editOp(1, 2, 3, 1, 1, 10, ['"x" : 1'])
);
});
@@ -424,10 +426,10 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'}'
],
[
- editOp(1, 2, 2, 1, 0, ['', '\t']),
- editOp(2, 11, 4, 1, 0, ['', '\t'])
+ editOp(1, 2, 2, 1, 1, 1, ['', '\t']),
+ editOp(2, 11, 4, 1, 12, 2, ['', '\t'])
],
- editOp(1, 2, 4, 1, 13, [
+ editOp(1, 2, 4, 1, 1, 13, [
'',
'\t"a": true,',
'\t'
@@ -446,12 +448,12 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
'and the last line'
],
[
- editOp(1, 5, 3, 1, 0, [' text', 'some more text', 'some more text']),
- editOp(3, 2, 4, 1, 0, ['o more lines', 'asd', 'asd', 'asd']),
- editOp(5, 1, 5, 6, 0, ['zzzzzzzz']),
- editOp(5, 11, 6, 16, 0, ['1', '2', '3', '4'])
+ editOp(1, 5, 3, 1, 4, 21, [' text', 'some more text', 'some more text']),
+ editOp(3, 2, 4, 1, 26, 23, ['o more lines', 'asd', 'asd', 'asd']),
+ editOp(5, 1, 5, 6, 50, 5, ['zzzzzzzz']),
+ editOp(5, 11, 6, 16, 60, 22, ['1', '2', '3', '4'])
],
- editOp(1, 5, 6, 16, 78, [
+ editOp(1, 5, 6, 16, 4, 78, [
' text',
'some more text',
'some more textno more lines',
@@ -475,17 +477,17 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
' ,"e": /*comment*/ [null] }',
],
[
- editOp(1, 1, 1, 2, 0, ['']),
- editOp(1, 3, 1, 10, 0, ['', ' ']),
- editOp(1, 16, 2, 14, 0, ['', ' ']),
- editOp(2, 18, 3, 9, 0, ['', ' ']),
- editOp(3, 22, 4, 9, 0, ['']),
- editOp(4, 10, 4, 10, 0, ['', ' ']),
- editOp(4, 28, 4, 28, 0, ['', ' ']),
- editOp(4, 32, 4, 32, 0, ['', ' ']),
- editOp(4, 33, 4, 34, 0, ['', ''])
+ editOp(1, 1, 1, 2, 0, 1, ['']),
+ editOp(1, 3, 1, 10, 2, 7, ['', ' ']),
+ editOp(1, 16, 2, 14, 15, 14, ['', ' ']),
+ editOp(2, 18, 3, 9, 33, 9, ['', ' ']),
+ editOp(3, 22, 4, 9, 55, 9, ['']),
+ editOp(4, 10, 4, 10, 65, 0, ['', ' ']),
+ editOp(4, 28, 4, 28, 83, 0, ['', ' ']),
+ editOp(4, 32, 4, 32, 87, 0, ['', ' ']),
+ editOp(4, 33, 4, 34, 88, 1, ['', ''])
],
- editOp(1, 1, 4, 34, 89, [
+ editOp(1, 1, 4, 34, 0, 89, [
'{',
' "d": [',
' null',
@@ -505,11 +507,11 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => {
' ,def'
],
[
- editOp(1, 1, 1, 4, 0, ['']),
- editOp(1, 7, 2, 2, 0, ['']),
- editOp(2, 3, 2, 3, 0, ['', ''])
+ editOp(1, 1, 1, 4, 0, 3, ['']),
+ editOp(1, 7, 2, 2, 6, 2, ['']),
+ editOp(2, 3, 2, 3, 9, 0, ['', ''])
],
- editOp(1, 1, 2, 3, 9, [
+ editOp(1, 1, 2, 3, 0, 9, [
'abc,',
''
])
@@ -1567,7 +1569,6 @@ suite('EditorModel - EditableTextModel.applyEdits', () => {
});
let assertMirrorModels = () => {
- model._assertLineNumbersOK();
assert.equal(mirrorModel2.getText(), model.getValue(), 'mirror model 2 text OK');
assert.equal(mirrorModel2.version, model.getVersionId(), 'mirror model 2 version OK');
};
@@ -1579,257 +1580,3 @@ suite('EditorModel - EditableTextModel.applyEdits', () => {
mirrorModel2.dispose();
});
});
-
-interface ILightWeightMarker {
- id: string;
- lineNumber: number;
- column: number;
- stickToPreviousCharacter: boolean;
-}
-
-suite('EditorModel - EditableTextModel.applyEdits & markers', () => {
-
- function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): IIdentifiedSingleEditOperation {
- return {
- identifier: null,
- range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
- text: text.join('\n'),
- forceMoveMarkers: false
- };
- }
-
- function marker(id: string, lineNumber: number, column: number, stickToPreviousCharacter: boolean): ILightWeightMarker {
- return {
- id: id,
- lineNumber: lineNumber,
- column: column,
- stickToPreviousCharacter: stickToPreviousCharacter
- };
- }
-
- function toMarkersMap(markers: ILightWeightMarker[]): { [markerId: string]: ILightWeightMarker } {
- var result: { [markerId: string]: ILightWeightMarker } = {};
- markers.forEach(m => {
- result[m.id] = m;
- });
- return result;
- }
-
- function testApplyEditsAndMarkers(text: string[], markers: ILightWeightMarker[], edits: IIdentifiedSingleEditOperation[], changedMarkers: string[], expectedText: string[], expectedMarkers: ILightWeightMarker[]): void {
- var textStr = text.join('\n');
- var expectedTextStr = expectedText.join('\n');
- var markersMap = toMarkersMap(markers);
- // var expectedMarkersMap = toMarkersMap(expectedMarkers);
- var markerId2ModelMarkerId = Object.create(null);
-
- var model = EditableTextModel.createFromString(textStr);
- model.setEOL(EndOfLineSequence.LF);
-
- // Add markers
- markers.forEach((m) => {
- let modelMarkerId = model._addMarker(0, m.lineNumber, m.column, m.stickToPreviousCharacter);
- markerId2ModelMarkerId[m.id] = modelMarkerId;
- });
-
- // Apply edits & collect inverse edits
- model.applyEdits(edits);
- model._assertLineNumbersOK();
-
- // Assert edits produced expected result
- assert.deepEqual(model.getValue(EndOfLinePreference.LF), expectedTextStr);
-
- let actualChangedMarkers: string[] = [];
- for (let i = 0, len = expectedMarkers.length; i < len; i++) {
- let expectedMarker = expectedMarkers[i];
- let initialMarker = markersMap[expectedMarker.id];
- let expectedMarkerModelMarkerId = markerId2ModelMarkerId[expectedMarker.id];
- let actualMarker = model._getMarker(expectedMarkerModelMarkerId);
-
- if (actualMarker.lineNumber !== initialMarker.lineNumber || actualMarker.column !== initialMarker.column) {
- actualChangedMarkers.push(initialMarker.id);
- }
-
- assert.equal(actualMarker.lineNumber, expectedMarker.lineNumber, 'marker lineNumber of marker ' + expectedMarker.id);
- assert.equal(actualMarker.column, expectedMarker.column, 'marker column of marker ' + expectedMarker.id);
- }
-
- changedMarkers.sort();
- actualChangedMarkers.sort();
- assert.deepEqual(actualChangedMarkers, changedMarkers, 'changed markers');
-
- model.dispose();
- }
-
- test('no markers changed', () => {
- testApplyEditsAndMarkers(
- [
- 'Hello world,',
- 'this is a short text',
- 'that is used in testing'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 2, 1, false),
- marker('f', 2, 16, true),
- marker('g', 2, 21, true),
- marker('h', 3, 24, false)
- ],
- [
- editOp(1, 13, 1, 13, [' how are you?'])
- ],
- [],
- [
- 'Hello world, how are you?',
- 'this is a short text',
- 'that is used in testing'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 2, 1, false),
- marker('f', 2, 16, true),
- marker('g', 2, 21, true),
- marker('h', 3, 24, false)
- ]
- );
- });
-
- test('first line changes', () => {
- testApplyEditsAndMarkers(
- [
- 'Hello world,',
- 'this is a short text',
- 'that is used in testing'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 2, 1, false),
- marker('f', 2, 16, true),
- marker('g', 2, 21, true),
- marker('h', 3, 24, false)
- ],
- [
- editOp(1, 7, 1, 12, ['friends'])
- ],
- [],
- [
- 'Hello friends,',
- 'this is a short text',
- 'that is used in testing'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 2, 1, false),
- marker('f', 2, 16, true),
- marker('g', 2, 21, true),
- marker('h', 3, 24, false)
- ]
- );
- });
-
- test('inserting lines', () => {
- testApplyEditsAndMarkers(
- [
- 'Hello world,',
- 'this is a short text',
- 'that is used in testing'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 2, 1, false),
- marker('f', 2, 16, true),
- marker('g', 2, 21, true),
- marker('h', 3, 24, false)
- ],
- [
- editOp(1, 7, 1, 12, ['friends']),
- editOp(1, 13, 1, 13, ['', 'this is an inserted line', 'and another one. By the way,'])
- ],
- ['e', 'f', 'g', 'h'],
- [
- 'Hello friends,',
- 'this is an inserted line',
- 'and another one. By the way,',
- 'this is a short text',
- 'that is used in testing'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 4, 1, false),
- marker('f', 4, 16, true),
- marker('g', 4, 21, true),
- marker('h', 5, 24, false)
- ]
- );
- });
-
- test('replacing a lot', () => {
- testApplyEditsAndMarkers(
- [
- 'Hello world,',
- 'this is a short text',
- 'that is used in testing',
- 'more lines...',
- 'more lines...',
- 'more lines...',
- 'more lines...'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 2, 1, false),
- marker('f', 2, 16, true),
- marker('g', 2, 21, true),
- marker('h', 3, 24, false),
- marker('i', 5, 1, false),
- marker('j', 6, 1, false),
- marker('k', 7, 14, false),
- ],
- [
- editOp(1, 7, 1, 12, ['friends']),
- editOp(1, 13, 1, 13, ['', 'this is an inserted line', 'and another one. By the way,', 'This is another line']),
- editOp(2, 1, 7, 14, ['Some new text here'])
- ],
- ['e', 'f', 'g', 'h', 'i', 'j', 'k'],
- [
- 'Hello friends,',
- 'this is an inserted line',
- 'and another one. By the way,',
- 'This is another line',
- 'Some new text here'
- ],
- [
- marker('a', 1, 1, true),
- marker('b', 1, 1, false),
- marker('c', 1, 7, false),
- marker('d', 1, 12, true),
- marker('e', 5, 1, false),
- marker('f', 5, 16, true),
- marker('g', 5, 19, true),
- marker('h', 5, 19, false),
- marker('i', 5, 19, false),
- marker('j', 5, 19, false),
- marker('k', 5, 19, false),
- ]
- );
- });
-});
diff --git a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts
index a901d29fcd326..b0b18723062fb 100644
--- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts
+++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts
@@ -105,7 +105,6 @@ export function assertSyncedModels(text: string, callback: (model: EditableTextM
var assertMirrorModels = () => {
assertLineMapping(model, 'model');
- model._assertLineNumbersOK();
assert.equal(mirrorModel2.getText(), model.getValue(), 'mirror model 2 text OK');
assert.equal(mirrorModel2.version, model.getVersionId(), 'mirror model 2 version OK');
};
diff --git a/src/vs/editor/test/common/model/intervalTree.test.ts b/src/vs/editor/test/common/model/intervalTree.test.ts
new file mode 100644
index 0000000000000..29a5978432e78
--- /dev/null
+++ b/src/vs/editor/test/common/model/intervalTree.test.ts
@@ -0,0 +1,555 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import * as assert from 'assert';
+import { IntervalTree, IntervalNode } from 'vs/editor/common/model/intervalTree';
+
+const GENERATE_TESTS = false;
+let TEST_COUNT = GENERATE_TESTS ? 10000 : 0;
+let PRINT_TREE = false;
+const MIN_INTERVAL_START = 1;
+const MAX_INTERVAL_END = 100;
+const MIN_INSERTS = 1;
+const MAX_INSERTS = 30;
+const MIN_CHANGE_CNT = 10;
+const MAX_CHANGE_CNT = 20;
+
+suite('IntervalTree', () => {
+
+ class Interval {
+ _intervalBrand: void;
+
+ public start: number;
+ public end: number;
+
+ constructor(start: number, end: number) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ class Oracle {
+ public intervals: Interval[];
+
+ constructor() {
+ this.intervals = [];
+ }
+
+ public insert(interval: Interval): Interval {
+ this.intervals.push(interval);
+ this.intervals.sort((a, b) => {
+ if (a.start === b.start) {
+ return a.end - b.end;
+ }
+ return a.start - b.start;
+ });
+ return interval;
+ }
+
+ public delete(interval: Interval): void {
+ for (let i = 0, len = this.intervals.length; i < len; i++) {
+ if (this.intervals[i] === interval) {
+ this.intervals.splice(i, 1);
+ return;
+ }
+ }
+ }
+
+ public search(interval: Interval): Interval[] {
+ let result: Interval[] = [];
+ for (let i = 0, len = this.intervals.length; i < len; i++) {
+ let int = this.intervals[i];
+ if (int.start <= interval.end && int.end >= interval.start) {
+ result.push(int);
+ }
+ }
+ return result;
+ }
+ }
+
+ class TestState {
+ private _oracle: Oracle = new Oracle();
+ private _tree: IntervalTree = new IntervalTree();
+ private _lastNodeId = -1;
+ private _treeNodes: IntervalNode[] = [];
+ private _oracleNodes: Interval[] = [];
+
+ public acceptOp(op: IOperation): void {
+
+ if (op.type === 'insert') {
+ if (PRINT_TREE) {
+ console.log(`insert: {${JSON.stringify(new Interval(op.begin, op.end))}}`);
+ }
+ let nodeId = (++this._lastNodeId);
+ this._treeNodes[nodeId] = new IntervalNode(null, op.begin, op.end);
+ this._tree.insert(this._treeNodes[nodeId]);
+ this._oracleNodes[nodeId] = this._oracle.insert(new Interval(op.begin, op.end));
+ } else if (op.type === 'delete') {
+ if (PRINT_TREE) {
+ console.log(`delete: {${JSON.stringify(this._oracleNodes[op.id])}}`);
+ }
+ this._tree.delete(this._treeNodes[op.id]);
+ this._oracle.delete(this._oracleNodes[op.id]);
+
+ this._treeNodes[op.id] = null;
+ this._oracleNodes[op.id] = null;
+ } else if (op.type === 'change') {
+
+ this._tree.delete(this._treeNodes[op.id]);
+ this._treeNodes[op.id].reset(0, op.begin, op.end, null);
+ this._tree.insert(this._treeNodes[op.id]);
+
+ this._oracle.delete(this._oracleNodes[op.id]);
+ this._oracleNodes[op.id].start = op.begin;
+ this._oracleNodes[op.id].end = op.end;
+ this._oracle.insert(this._oracleNodes[op.id]);
+
+ } else {
+ let actualNodes = this._tree.intervalSearch(op.begin, op.end, 0, false, 0);
+ let actual = actualNodes.map(n => new Interval(n.cachedAbsoluteStart, n.cachedAbsoluteEnd));
+ let expected = this._oracle.search(new Interval(op.begin, op.end));
+ assert.deepEqual(actual, expected);
+ return;
+ }
+
+ if (PRINT_TREE) {
+ this._tree.print();
+ }
+
+ this._tree.assertInvariants();
+
+ let actual = this._tree.getAllInOrder().map(n => new Interval(n.cachedAbsoluteStart, n.cachedAbsoluteEnd));
+ let expected = this._oracle.intervals;
+ assert.deepEqual(actual, expected);
+ }
+
+ public getExistingNodeId(index: number): number {
+ let currIndex = -1;
+ for (let i = 0; i < this._treeNodes.length; i++) {
+ if (this._treeNodes[i] === null) {
+ continue;
+ }
+ currIndex++;
+ if (currIndex === index) {
+ return i;
+ }
+ }
+ throw new Error('unexpected');
+ }
+ }
+
+ interface IInsertOperation {
+ type: 'insert';
+ begin: number;
+ end: number;
+ }
+
+ interface IDeleteOperation {
+ type: 'delete';
+ id: number;
+ }
+
+ interface IChangeOperation {
+ type: 'change';
+ id: number;
+ begin: number;
+ end: number;
+ }
+
+ interface ISearchOperation {
+ type: 'search';
+ begin: number;
+ end: number;
+ }
+
+ type IOperation = IInsertOperation | IDeleteOperation | IChangeOperation | ISearchOperation;
+
+ function testIntervalTree(ops: IOperation[]): void {
+ let state = new TestState();
+ for (let i = 0; i < ops.length; i++) {
+ state.acceptOp(ops[i]);
+ }
+ }
+
+ function getRandomInt(min: number, max: number): number {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ function getRandomRange(min: number, max: number): [number, number] {
+ let begin = getRandomInt(min, max);
+ let length: number;
+ if (getRandomInt(1, 10) <= 2) {
+ // large range
+ length = getRandomInt(0, max - begin);
+ } else {
+ // small range
+ length = getRandomInt(0, Math.min(max - begin, 10));
+ }
+ return [begin, begin + length];
+ }
+
+ class AutoTest {
+ private _ops: IOperation[] = [];
+ private _state: TestState = new TestState();
+ private _insertCnt: number;
+ private _deleteCnt: number;
+ private _changeCnt: number;
+
+ constructor() {
+ this._insertCnt = getRandomInt(MIN_INSERTS, MAX_INSERTS);
+ this._changeCnt = getRandomInt(MIN_CHANGE_CNT, MAX_CHANGE_CNT);
+ this._deleteCnt = 0;
+ }
+
+ private _doRandomInsert(): void {
+ let range = getRandomRange(MIN_INTERVAL_START, MAX_INTERVAL_END);
+ this._run({
+ type: 'insert',
+ begin: range[0],
+ end: range[1]
+ });
+ }
+
+ private _doRandomDelete(): void {
+ let idx = getRandomInt(Math.floor(this._deleteCnt / 2), this._deleteCnt - 1);
+ this._run({
+ type: 'delete',
+ id: this._state.getExistingNodeId(idx)
+ });
+ }
+
+ private _doRandomChange(): void {
+ let idx = getRandomInt(0, this._deleteCnt - 1);
+ let range = getRandomRange(MIN_INTERVAL_START, MAX_INTERVAL_END);
+ this._run({
+ type: 'change',
+ id: this._state.getExistingNodeId(idx),
+ begin: range[0],
+ end: range[1]
+ });
+ }
+
+ public run() {
+ while (this._insertCnt > 0 || this._deleteCnt > 0 || this._changeCnt > 0) {
+ if (this._insertCnt > 0) {
+ this._doRandomInsert();
+ this._insertCnt--;
+ this._deleteCnt++;
+ } else if (this._changeCnt > 0) {
+ this._doRandomChange();
+ this._changeCnt--;
+ } else {
+ this._doRandomDelete();
+ this._deleteCnt--;
+ }
+
+ // Let's also search for something...
+ let searchRange = getRandomRange(MIN_INTERVAL_START, MAX_INTERVAL_END);
+ this._run({
+ type: 'search',
+ begin: searchRange[0],
+ end: searchRange[1]
+ });
+ }
+ }
+
+ private _run(op: IOperation): void {
+ this._ops.push(op);
+ this._state.acceptOp(op);
+ }
+
+ public print(): void {
+ console.log(`testIntervalTree(${JSON.stringify(this._ops)})`);
+ }
+
+ }
+
+ suite('generated', () => {
+ test('gen01', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 28, end: 35 },
+ { type: 'insert', begin: 52, end: 54 },
+ { type: 'insert', begin: 63, end: 69 }
+ ]);
+ });
+
+ test('gen02', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 80, end: 89 },
+ { type: 'insert', begin: 92, end: 100 },
+ { type: 'insert', begin: 99, end: 99 }
+ ]);
+ });
+
+ test('gen03', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 89, end: 96 },
+ { type: 'insert', begin: 71, end: 74 },
+ { type: 'delete', id: 1 }
+ ]);
+ });
+
+ test('gen04', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 44, end: 46 },
+ { type: 'insert', begin: 85, end: 88 },
+ { type: 'delete', id: 0 }
+ ]);
+ });
+
+ test('gen05', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 82, end: 90 },
+ { type: 'insert', begin: 69, end: 73 },
+ { type: 'delete', id: 0 },
+ { type: 'delete', id: 1 }
+ ]);
+ });
+
+ test('gen06', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 41, end: 63 },
+ { type: 'insert', begin: 98, end: 98 },
+ { type: 'insert', begin: 47, end: 51 },
+ { type: 'delete', id: 2 }
+ ]);
+ });
+
+ test('gen07', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 24, end: 26 },
+ { type: 'insert', begin: 11, end: 28 },
+ { type: 'insert', begin: 27, end: 30 },
+ { type: 'insert', begin: 80, end: 85 },
+ { type: 'delete', id: 1 }
+ ]);
+ });
+
+ test('gen08', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 100, end: 100 },
+ { type: 'insert', begin: 100, end: 100 }
+ ]);
+ });
+
+ test('gen09', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 58, end: 65 },
+ { type: 'insert', begin: 82, end: 96 },
+ { type: 'insert', begin: 58, end: 65 }
+ ]);
+ });
+
+ test('gen10', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 32, end: 40 },
+ { type: 'insert', begin: 25, end: 29 },
+ { type: 'insert', begin: 24, end: 32 }
+ ]);
+ });
+
+ test('gen11', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 25, end: 70 },
+ { type: 'insert', begin: 99, end: 100 },
+ { type: 'insert', begin: 46, end: 51 },
+ { type: 'insert', begin: 57, end: 57 },
+ { type: 'delete', id: 2 }
+ ]);
+ });
+
+ test('gen12', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 20, end: 26 },
+ { type: 'insert', begin: 10, end: 18 },
+ { type: 'insert', begin: 99, end: 99 },
+ { type: 'insert', begin: 37, end: 59 },
+ { type: 'delete', id: 2 }
+ ]);
+ });
+
+ test('gen13', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 3, end: 91 },
+ { type: 'insert', begin: 57, end: 57 },
+ { type: 'insert', begin: 35, end: 44 },
+ { type: 'insert', begin: 72, end: 81 },
+ { type: 'delete', id: 2 }
+ ]);
+ });
+
+ test('gen14', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 58, end: 61 },
+ { type: 'insert', begin: 34, end: 35 },
+ { type: 'insert', begin: 56, end: 62 },
+ { type: 'insert', begin: 69, end: 78 },
+ { type: 'delete', id: 0 }
+ ]);
+ });
+
+ test('gen15', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 63, end: 69 },
+ { type: 'insert', begin: 17, end: 24 },
+ { type: 'insert', begin: 3, end: 13 },
+ { type: 'insert', begin: 84, end: 94 },
+ { type: 'insert', begin: 18, end: 23 },
+ { type: 'insert', begin: 96, end: 98 },
+ { type: 'delete', id: 1 }
+ ]);
+ });
+
+ test('gen16', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 27, end: 27 },
+ { type: 'insert', begin: 42, end: 87 },
+ { type: 'insert', begin: 42, end: 49 },
+ { type: 'insert', begin: 69, end: 71 },
+ { type: 'insert', begin: 20, end: 27 },
+ { type: 'insert', begin: 8, end: 9 },
+ { type: 'insert', begin: 42, end: 49 },
+ { type: 'delete', id: 1 }
+ ]);
+ });
+
+ test('gen17', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 21, end: 23 },
+ { type: 'insert', begin: 83, end: 87 },
+ { type: 'insert', begin: 56, end: 58 },
+ { type: 'insert', begin: 1, end: 55 },
+ { type: 'insert', begin: 56, end: 59 },
+ { type: 'insert', begin: 58, end: 60 },
+ { type: 'insert', begin: 56, end: 65 },
+ { type: 'delete', id: 1 },
+ { type: 'delete', id: 0 },
+ { type: 'delete', id: 6 }
+ ]);
+ });
+
+ test('gen18', () => {
+ testIntervalTree([
+ { type: 'insert', begin: 25, end: 25 },
+ { type: 'insert', begin: 67, end: 79 },
+ { type: 'delete', id: 0 },
+ { type: 'search', begin: 65, end: 75 }
+ ]);
+ });
+
+ test('force delta overflow', () => {
+ // Search the IntervalNode ctor for FORCE_OVERFLOWING_TEST
+ // to force that this test leads to a delta normalization
+ testIntervalTree([
+ { type: 'insert', begin: 686081138593427, end: 733009856502260 },
+ { type: 'insert', begin: 591031326181669, end: 591031326181672 },
+ { type: 'insert', begin: 940037682731896, end: 940037682731903 },
+ { type: 'insert', begin: 598413641151120, end: 598413641151128 },
+ { type: 'insert', begin: 800564156553344, end: 800564156553351 },
+ { type: 'insert', begin: 894198957565481, end: 894198957565491 }
+ ]);
+ });
+ });
+
+ // TEST_COUNT = 0;
+ // PRINT_TREE = true;
+
+ for (let i = 0; i < TEST_COUNT; i++) {
+ if (i % 100 === 0) {
+ console.log(`TEST ${i + 1}/${TEST_COUNT}`);
+ }
+ let test = new AutoTest();
+
+ try {
+ test.run();
+ } catch (err) {
+ console.log(err);
+ test.print();
+ return;
+ }
+ }
+
+ suite('searching', () => {
+
+ function createCormenTree(): IntervalTree {
+ let r = new IntervalTree();
+ let data: [number, number][] = [
+ [16, 21],
+ [8, 9],
+ [25, 30],
+ [5, 8],
+ [15, 23],
+ [17, 19],
+ [26, 26],
+ [0, 3],
+ [6, 10],
+ [19, 20]
+ ];
+ data.forEach((int) => {
+ let node = new IntervalNode(null, int[0], int[1]);
+ r.insert(node);
+ });
+ return r;
+ }
+
+ const T = createCormenTree();
+
+ function assertIntervalSearch(start: number, end: number, expected: [number, number][]): void {
+ let actualNodes = T.intervalSearch(start, end, 0, false, 0);
+ let actual = actualNodes.map((n) => <[number, number]>[n.cachedAbsoluteStart, n.cachedAbsoluteEnd]);
+ assert.deepEqual(actual, expected);
+ }
+
+ test('cormen 1->2', () => {
+ assertIntervalSearch(
+ 1, 2,
+ [
+ [0, 3],
+ ]
+ );
+ });
+
+ test('cormen 4->8', () => {
+ assertIntervalSearch(
+ 4, 8,
+ [
+ [5, 8],
+ [6, 10],
+ [8, 9],
+ ]
+ );
+ });
+
+ test('cormen 10->15', () => {
+ assertIntervalSearch(
+ 10, 15,
+ [
+ [6, 10],
+ [15, 23],
+ ]
+ );
+ });
+
+ test('cormen 21->25', () => {
+ assertIntervalSearch(
+ 21, 25,
+ [
+ [15, 23],
+ [16, 21],
+ [25, 30],
+ ]
+ );
+ });
+
+ test('cormen 24->24', () => {
+ assertIntervalSearch(
+ 24, 24,
+ [
+ ]
+ );
+ });
+ });
+});
diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts
index 81dbba468d517..a2e75ba4ae938 100644
--- a/src/vs/editor/test/common/model/model.line.test.ts
+++ b/src/vs/editor/test/common/model/model.line.test.ts
@@ -6,9 +6,8 @@
import * as assert from 'assert';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
-import { ModelLine, ILineEdit, LineMarker, MarkersTracker } from 'vs/editor/common/model/modelLine';
+import { ModelLine, ILineEdit } from 'vs/editor/common/model/modelLine';
import { MetadataConsts } from 'vs/editor/common/modes';
-import { Position } from 'vs/editor/common/core/position';
import { ViewLineToken, ViewLineTokenFactory } from 'vs/editor/common/core/viewLineToken';
function assertLineTokens(_actual: LineTokens, _expected: TestToken[]): void {
@@ -54,7 +53,7 @@ suite('Editor Model - modelLine.applyEdits text', () => {
function testEdits(initial: string, edits: ILineEdit[], expected: string): void {
var line = new ModelLine(initial, NO_TAB_SIZE);
- line.applyEdits(new MarkersTracker(), edits, NO_TAB_SIZE);
+ line.applyEdits(edits, NO_TAB_SIZE);
assert.equal(line.text, expected);
}
@@ -62,8 +61,7 @@ suite('Editor Model - modelLine.applyEdits text', () => {
return {
startColumn: startColumn,
endColumn: endColumn,
- text: text,
- forceMoveMarkers: false
+ text: text
};
}
@@ -201,7 +199,7 @@ suite('Editor Model - modelLine.split text', () => {
function testLineSplit(initial: string, splitColumn: number, expected1: string, expected2: string): void {
var line = new ModelLine(initial, NO_TAB_SIZE);
- var newLine = line.split(new MarkersTracker(), splitColumn, false, NO_TAB_SIZE);
+ var newLine = line.split(splitColumn, NO_TAB_SIZE);
assert.equal(line.text, expected1);
assert.equal(newLine.text, expected2);
}
@@ -239,7 +237,7 @@ suite('Editor Model - modelLine.append text', () => {
function testLineAppend(a: string, b: string, expected: string): void {
var line1 = new ModelLine(a, NO_TAB_SIZE);
var line2 = new ModelLine(b, NO_TAB_SIZE);
- line1.append(new MarkersTracker(), 1, line2, NO_TAB_SIZE);
+ line1.append(line2, NO_TAB_SIZE);
assert.equal(line1.text, expected);
}
@@ -301,7 +299,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
let line = new ModelLine(initialText, NO_TAB_SIZE);
line.setTokens(0, TestToken.toTokens(initialTokens));
- line.applyEdits(new MarkersTracker(), edits, NO_TAB_SIZE);
+ line.applyEdits(edits, NO_TAB_SIZE);
assert.equal(line.text, expectedText);
assertLineTokens(line.getTokens(0), expectedTokens);
@@ -311,10 +309,10 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
let line = new ModelLine('some text', NO_TAB_SIZE);
line.setTokens(0, TestToken.toTokens([new TestToken(0, 1)]));
- line.applyEdits(new MarkersTracker(), [{ startColumn: 1, endColumn: 10, text: '', forceMoveMarkers: false }], NO_TAB_SIZE);
+ line.applyEdits([{ startColumn: 1, endColumn: 10, text: '' }], NO_TAB_SIZE);
line.setTokens(0, new Uint32Array(0));
- line.applyEdits(new MarkersTracker(), [{ startColumn: 1, endColumn: 1, text: 'a', forceMoveMarkers: false }], NO_TAB_SIZE);
+ line.applyEdits([{ startColumn: 1, endColumn: 1, text: 'a' }], NO_TAB_SIZE);
assertLineTokens(line.getTokens(0), [new TestToken(0, 1)]);
});
@@ -330,7 +328,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}],
'aabcd efgh',
[
@@ -353,7 +350,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 2,
endColumn: 2,
text: 'x',
- forceMoveMarkers: false
}],
'axabcd efgh',
[
@@ -376,7 +372,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 3,
endColumn: 3,
text: 'stu',
- forceMoveMarkers: false
}],
'axstuabcd efgh',
[
@@ -399,7 +394,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 10,
endColumn: 10,
text: '\t',
- forceMoveMarkers: false
}],
'axstuabcd\t efgh',
[
@@ -422,7 +416,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 12,
endColumn: 12,
text: 'dd',
- forceMoveMarkers: false
}],
'axstuabcd\t ddefgh',
[
@@ -445,7 +438,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 18,
endColumn: 18,
text: 'xyz',
- forceMoveMarkers: false
}],
'axstuabcd\t ddefghxyz',
[
@@ -468,7 +460,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 1,
text: 'x',
- forceMoveMarkers: false
}],
'xaxstuabcd\t ddefghxyz',
[
@@ -491,7 +482,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 22,
endColumn: 22,
text: 'x',
- forceMoveMarkers: false
}],
'xaxstuabcd\t ddefghxyzx',
[
@@ -514,7 +504,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 2,
endColumn: 2,
text: '',
- forceMoveMarkers: false
}],
'xaxstuabcd\t ddefghxyzx',
[
@@ -533,7 +522,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}],
'a',
[
@@ -554,7 +542,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 4,
endColumn: 7,
text: '',
- forceMoveMarkers: false
}],
'abcghij',
[
@@ -576,7 +563,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 4,
endColumn: 4,
text: 'hello',
- forceMoveMarkers: false
}],
'abchellodefghij',
[
@@ -599,7 +585,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 2,
text: '',
- forceMoveMarkers: false
}],
'bcd efgh',
[
@@ -622,7 +607,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 2,
endColumn: 4,
text: '',
- forceMoveMarkers: false
}],
'ad efgh',
[
@@ -645,7 +629,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 5,
text: '',
- forceMoveMarkers: false
}],
' efgh',
[
@@ -667,7 +650,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 5,
endColumn: 6,
text: '',
- forceMoveMarkers: false
}],
'abcdefgh',
[
@@ -689,7 +671,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 5,
endColumn: 7,
text: '',
- forceMoveMarkers: false
}],
'abcdfgh',
[
@@ -711,7 +692,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 5,
endColumn: 10,
text: '',
- forceMoveMarkers: false
}],
'abcd',
[
@@ -732,7 +712,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 10,
text: '',
- forceMoveMarkers: false
}],
'',
[
@@ -753,7 +732,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 1,
text: '',
- forceMoveMarkers: false
}],
'abcd efgh',
[
@@ -776,7 +754,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 3,
text: '',
- forceMoveMarkers: false
}],
'cd efgh',
[
@@ -799,7 +776,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 5,
endColumn: 10,
text: '',
- forceMoveMarkers: false
}],
'abcd',
[
@@ -822,7 +798,6 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 6,
text: 'Hi',
- forceMoveMarkers: false
}],
'Hi world, ciao',
[
@@ -849,12 +824,10 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => {
startColumn: 1,
endColumn: 6,
text: 'Hi',
- forceMoveMarkers: false
}, {
startColumn: 8,
endColumn: 12,
text: 'my friends',
- forceMoveMarkers: false
}],
'Hi wmy friends, ciao',
[
@@ -873,7 +846,7 @@ suite('Editor Model - modelLine.split text & tokens', () => {
let line = new ModelLine(initialText, NO_TAB_SIZE);
line.setTokens(0, TestToken.toTokens(initialTokens));
- let other = line.split(new MarkersTracker(), splitColumn, false, NO_TAB_SIZE);
+ let other = line.split(splitColumn, NO_TAB_SIZE);
assert.equal(line.text, expectedText1);
assert.equal(other.text, expectedText2);
@@ -960,7 +933,7 @@ suite('Editor Model - modelLine.append text & tokens', () => {
let b = new ModelLine(bText, NO_TAB_SIZE);
b.setTokens(0, TestToken.toTokens(bTokens));
- a.append(new MarkersTracker(), 1, b, NO_TAB_SIZE);
+ a.append(b, NO_TAB_SIZE);
assert.equal(a.text, expectedText);
assertLineTokens(a.getTokens(0), expectedTokens);
@@ -1071,1260 +1044,483 @@ suite('Editor Model - modelLine.append text & tokens', () => {
});
});
-interface ILightWeightMarker {
- id: string;
- lineNumber: number;
- column: number;
- stickToPreviousCharacter: boolean;
-}
-
-suite('Editor Model - modelLine.applyEdits text & markers', () => {
-
- function marker(id: number, column: number, stickToPreviousCharacter: boolean): LineMarker {
- return new LineMarker(String(id), id, new Position(0, column), stickToPreviousCharacter);
- }
-
- function toLightWeightMarker(marker: LineMarker): ILightWeightMarker {
- return {
- id: marker.id,
- lineNumber: marker.position.lineNumber,
- column: marker.position.column,
- stickToPreviousCharacter: marker.stickToPreviousCharacter
- };
- }
+suite('Editor Model - modelLine.applyEdits', () => {
- function testLineEditMarkers(initialText: string, initialMarkers: LineMarker[], edits: ILineEdit[], expectedText: string, expectedChangedMarkers: number[], _expectedMarkers: LineMarker[]): void {
+ function testLineEdit(initialText: string, edits: ILineEdit[], expectedText: string): void {
let line = new ModelLine(initialText, NO_TAB_SIZE);
- line.addMarkers(initialMarkers);
- let changedMarkers = new MarkersTracker();
- line.applyEdits(changedMarkers, edits, NO_TAB_SIZE);
+ line.applyEdits(edits, NO_TAB_SIZE);
assert.equal(line.text, expectedText, 'text');
-
- let actualMarkers = line.getMarkers().map(toLightWeightMarker);
- let expectedMarkers = _expectedMarkers.map(toLightWeightMarker);
- assert.deepEqual(actualMarkers, expectedMarkers, 'markers');
-
- let actualChangedMarkers = changedMarkers.getDecorationIds();
- actualChangedMarkers.sort();
- assert.deepEqual(actualChangedMarkers, expectedChangedMarkers, 'changed markers');
}
test('insertion: updates markers 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 1,
endColumn: 1,
text: 'abc',
- forceMoveMarkers: false
}],
'abcabcd efgh',
- [2, 3, 4, 5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 4, false),
- marker(3, 5, true),
- marker(4, 5, false),
- marker(5, 8, true),
- marker(6, 8, false),
- marker(7, 13, true),
- marker(8, 13, false)
- ]
);
});
test('insertion: updates markers 2', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 2,
endColumn: 2,
text: 'abc',
- forceMoveMarkers: false
}],
'aabcbcd efgh',
- [4, 5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 5, false),
- marker(5, 8, true),
- marker(6, 8, false),
- marker(7, 13, true),
- marker(8, 13, false)
- ]
);
});
test('insertion: updates markers 3', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 3,
endColumn: 3,
text: 'abc',
- forceMoveMarkers: false
}],
'ababccd efgh',
- [5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 8, true),
- marker(6, 8, false),
- marker(7, 13, true),
- marker(8, 13, false)
- ]
);
});
test('insertion: updates markers 4', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 5,
endColumn: 5,
text: 'abc',
- forceMoveMarkers: false
}],
'abcdabc efgh',
- [6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 8, false),
- marker(7, 13, true),
- marker(8, 13, false)
- ]
);
});
test('insertion: updates markers 5', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 10,
endColumn: 10,
text: 'abc',
- forceMoveMarkers: false
}],
'abcd efghabc',
- [8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 13, false)
- ]
);
});
test('insertion bis: updates markers 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}],
'aabcd efgh',
- [2, 3, 4, 5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 2, false),
- marker(3, 3, true),
- marker(4, 3, false),
- marker(5, 6, true),
- marker(6, 6, false),
- marker(7, 11, true),
- marker(8, 11, false)
- ]
);
});
test('insertion bis: updates markers 2', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 2,
endColumn: 2,
text: 'a',
- forceMoveMarkers: false
}],
'aabcd efgh',
- [4, 5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 3, false),
- marker(5, 6, true),
- marker(6, 6, false),
- marker(7, 11, true),
- marker(8, 11, false)
- ]
);
});
test('insertion bis: updates markers 3', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 3,
endColumn: 3,
text: 'a',
- forceMoveMarkers: false
}],
'abacd efgh',
- [5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 6, true),
- marker(6, 6, false),
- marker(7, 11, true),
- marker(8, 11, false)
- ]
);
});
test('insertion bis: updates markers 4', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 5,
endColumn: 5,
text: 'a',
- forceMoveMarkers: false
}],
'abcda efgh',
- [6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 6, false),
- marker(7, 11, true),
- marker(8, 11, false)
- ]
);
});
test('insertion bis: updates markers 5', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 10,
endColumn: 10,
text: 'a',
- forceMoveMarkers: false
}],
'abcd efgha',
- [8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 11, false)
- ]
);
});
test('insertion: does not move marker at column 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [marker(1, 1, true)],
[{
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}],
'aabcd efgh',
- [],
- [marker(1, 1, true)]
);
});
test('insertion: does move marker at column 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [marker(1, 1, false)],
[{
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}],
'aabcd efgh',
- [1],
- [marker(1, 2, false)]
);
});
test('insertion: two markers at column 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- ],
[{
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}],
'aabcd efgh',
- [2],
- [
- marker(1, 1, true),
- marker(2, 2, false)
- ]
);
});
test('insertion: two markers at column 1 unsorted', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(2, 1, false),
- marker(1, 1, true),
- ],
[{
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}],
'aabcd efgh',
- [2],
- [
- marker(1, 1, true),
- marker(2, 2, false)
- ]
);
});
test('deletion: updates markers 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 1,
endColumn: 2,
text: '',
- forceMoveMarkers: false
}],
'bcd efgh',
- [3, 4, 5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 1, true),
- marker(4, 1, false),
- marker(5, 4, true),
- marker(6, 4, false),
- marker(7, 9, true),
- marker(8, 9, false)
- ]
);
});
test('deletion: updates markers 2', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 1,
endColumn: 4,
text: '',
- forceMoveMarkers: false
}],
'd efgh',
- [3, 4, 5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 1, true),
- marker(4, 1, false),
- marker(5, 2, true),
- marker(6, 2, false),
- marker(7, 7, true),
- marker(8, 7, false)
- ]
);
});
test('deletion: updates markers 3', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 5,
endColumn: 6,
text: '',
- forceMoveMarkers: false
}],
'abcdefgh',
- [7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 9, true),
- marker(8, 9, false)
- ]
);
});
test('replace: updates markers 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
[{
startColumn: 1,
endColumn: 1,
text: 'a',
- forceMoveMarkers: false
}, {
startColumn: 2,
endColumn: 3,
text: '',
- forceMoveMarkers: false
}],
'aacd efgh',
- [2, 3, 4],
- [
- marker(1, 1, true),
- marker(2, 2, false),
- marker(3, 3, true),
- marker(4, 3, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ]
);
});
test('delete near markers', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd',
- [
- marker(1, 3, true),
- marker(2, 3, false)
- ],
[{
startColumn: 3,
endColumn: 4,
text: '',
- forceMoveMarkers: false
}],
'abd',
- [],
- [
- marker(1, 3, true),
- marker(2, 3, false)
- ]
);
});
test('replace: updates markers 2', () => {
- testLineEditMarkers(
+ testLineEdit(
'Hello world, how are you',
- [
- marker(1, 1, false),
- marker(2, 6, true),
- marker(3, 14, false),
- marker(4, 21, true)
- ],
[{
startColumn: 1,
endColumn: 1,
text: ' - ',
- forceMoveMarkers: false
}, {
startColumn: 6,
endColumn: 12,
text: '',
- forceMoveMarkers: false
}, {
startColumn: 22,
endColumn: 25,
text: 'things',
- forceMoveMarkers: false
}],
' - Hello, how are things',
- [1, 2, 3, 4],
- [
- marker(1, 4, false),
- marker(2, 9, true),
- marker(3, 11, false),
- marker(4, 18, true)
- ]
);
});
test('sorts markers', () => {
- testLineEditMarkers(
+ testLineEdit(
'Hello world, how are you',
- [
- marker(4, 21, true),
- marker(2, 6, true),
- marker(1, 1, false),
- marker(3, 14, false)
- ],
[{
startColumn: 1,
endColumn: 1,
text: ' - ',
- forceMoveMarkers: false
}, {
startColumn: 6,
endColumn: 12,
text: '',
- forceMoveMarkers: false
}, {
startColumn: 22,
endColumn: 25,
text: 'things',
- forceMoveMarkers: false
}],
' - Hello, how are things',
- [1, 2, 3, 4],
- [
- marker(1, 4, false),
- marker(2, 9, true),
- marker(3, 11, false),
- marker(4, 18, true)
- ]
);
});
test('change text inside markers', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 6, false),
- marker(4, 10, true)
- ],
[{
startColumn: 6,
endColumn: 10,
text: '1234567',
- forceMoveMarkers: false
}],
'abcd 1234567',
- [],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 6, false),
- marker(4, 10, true)
- ]
);
});
test('inserting is different than replacing for markers part 1', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd',
- [
- marker(1, 2, false)
- ],
[{
startColumn: 2,
endColumn: 2,
text: 'INSERT',
- forceMoveMarkers: false
}],
'aINSERTbcd',
- [1],
- [
- marker(1, 8, false)
- ]
);
});
test('inserting is different than replacing for markers part 2', () => {
- testLineEditMarkers(
+ testLineEdit(
'abcd',
- [
- marker(1, 2, false)
- ],
[{
startColumn: 2,
endColumn: 3,
text: 'REPLACED',
- forceMoveMarkers: false
}],
'aREPLACEDcd',
- [],
- [
- marker(1, 2, false)
- ]
);
});
test('replacing the entire line with more text', () => {
- testLineEditMarkers(
+ testLineEdit(
'this is a short text',
- [
- marker(1, 1, false),
- marker(2, 16, true),
- ],
[{
startColumn: 1,
endColumn: 21,
text: 'Some new text here',
- forceMoveMarkers: false
}],
'Some new text here',
- [],
- [
- marker(1, 1, false),
- marker(2, 16, true),
- ]
);
});
test('replacing the entire line with less text', () => {
- testLineEditMarkers(
+ testLineEdit(
'this is a short text',
- [
- marker(1, 1, false),
- marker(2, 16, true),
- ],
[{
startColumn: 1,
endColumn: 21,
text: 'ttt',
- forceMoveMarkers: false
}],
'ttt',
- [2],
- [
- marker(1, 1, false),
- marker(2, 4, true),
- ]
);
});
test('replace selection', () => {
- testLineEditMarkers(
+ testLineEdit(
'first',
- [
- marker(1, 1, true),
- marker(2, 6, false),
- ],
[{
startColumn: 1,
endColumn: 6,
text: 'something',
- forceMoveMarkers: false
}],
'something',
- [2],
- [
- marker(1, 1, true),
- marker(2, 10, false),
- ]
);
});
});
-suite('Editor Model - modelLine.split text & markers', () => {
-
- function marker(id: number, column: number, stickToPreviousCharacter: boolean): LineMarker {
- return new LineMarker(String(id), id, new Position(0, column), stickToPreviousCharacter);
- }
-
- function toLightWeightMarker(marker: LineMarker): ILightWeightMarker {
- return {
- id: marker.id,
- lineNumber: marker.position.lineNumber,
- column: marker.position.column,
- stickToPreviousCharacter: marker.stickToPreviousCharacter
- };
- }
+suite('Editor Model - modelLine.split', () => {
- function testLineSplitMarkers(initialText: string, initialMarkers: LineMarker[], splitColumn: number, forceMoveMarkers: boolean, expectedText1: string, expectedText2: string, expectedChangedMarkers: number[], _expectedMarkers1: LineMarker[], _expectedMarkers2: LineMarker[]): void {
+ function testLineSplit(initialText: string, splitColumn: number, forceMoveMarkers: boolean, expectedText1: string, expectedText2: string): void {
let line = new ModelLine(initialText, NO_TAB_SIZE);
- line.addMarkers(initialMarkers);
- let changedMarkers = new MarkersTracker();
- let otherLine = line.split(changedMarkers, splitColumn, forceMoveMarkers, NO_TAB_SIZE);
+ let otherLine = line.split(splitColumn, NO_TAB_SIZE);
assert.equal(line.text, expectedText1, 'text');
assert.equal(otherLine.text, expectedText2, 'text');
-
- let actualMarkers1 = line.getMarkers().map(toLightWeightMarker);
- let expectedMarkers1 = _expectedMarkers1.map(toLightWeightMarker);
- assert.deepEqual(actualMarkers1, expectedMarkers1, 'markers');
-
- let actualMarkers2 = otherLine.getMarkers().map(toLightWeightMarker);
- let expectedMarkers2 = _expectedMarkers2.map(toLightWeightMarker);
- assert.deepEqual(actualMarkers2, expectedMarkers2, 'markers');
-
- let actualChangedMarkers = changedMarkers.getDecorationIds();
- actualChangedMarkers.sort();
- assert.deepEqual(actualChangedMarkers, expectedChangedMarkers, 'changed markers');
}
test('split at the beginning', () => {
- testLineSplitMarkers(
+ testLineSplit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
1,
false,
'',
'abcd efgh',
- [],
- [
- marker(1, 1, true)
- ],
- [
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ]
);
});
test('split at the beginning 2', () => {
- testLineSplitMarkers(
+ testLineSplit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
1,
true,
'',
'abcd efgh',
- [],
- [],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ]
);
});
test('split at the end', () => {
- testLineSplitMarkers(
+ testLineSplit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
10,
false,
'abcd efgh',
'',
- [8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- ],
- [
- marker(8, 1, false)
- ]
);
});
test('split it the middle 1', () => {
- testLineSplitMarkers(
+ testLineSplit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
2,
false,
'a',
'bcd efgh',
- [4, 5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- ],
- [
- marker(4, 1, false),
- marker(5, 4, true),
- marker(6, 4, false),
- marker(7, 9, true),
- marker(8, 9, false)
- ]
);
});
test('split it the middle 2', () => {
- testLineSplitMarkers(
+ testLineSplit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
3,
false,
'ab',
'cd efgh',
- [5, 6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- ],
- [
- marker(5, 3, true),
- marker(6, 3, false),
- marker(7, 8, true),
- marker(8, 8, false)
- ]
);
});
test('split it the middle 3', () => {
- testLineSplitMarkers(
+ testLineSplit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
5,
false,
'abcd',
' efgh',
- [6, 7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- ],
- [
- marker(6, 1, false),
- marker(7, 6, true),
- marker(8, 6, false)
- ]
);
});
test('split it the middle 4', () => {
- testLineSplitMarkers(
+ testLineSplit(
'abcd efgh',
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- marker(7, 10, true),
- marker(8, 10, false)
- ],
6,
false,
'abcd ',
'efgh',
- [7, 8],
- [
- marker(1, 1, true),
- marker(2, 1, false),
- marker(3, 2, true),
- marker(4, 2, false),
- marker(5, 5, true),
- marker(6, 5, false),
- ],
- [
- marker(7, 5, true),
- marker(8, 5, false)
- ]
);
});
});
-suite('Editor Model - modelLine.append text & markers', () => {
-
- function markerOnFirstLine(id: number, column: number, stickToPreviousCharacter: boolean): LineMarker {
- return new LineMarker(String(id), id, new Position(1, column), stickToPreviousCharacter);
- }
-
- function markerOnSecondLine(id: number, column: number, stickToPreviousCharacter: boolean): LineMarker {
- return new LineMarker(String(id), id, new Position(2, column), stickToPreviousCharacter);
- }
-
- function toLightWeightMarker(marker: LineMarker): ILightWeightMarker {
- return {
- id: marker.id,
- lineNumber: marker.position.lineNumber,
- column: marker.position.column,
- stickToPreviousCharacter: marker.stickToPreviousCharacter
- };
- }
+suite('Editor Model - modelLine.append', () => {
- function testLinePrependMarkers(aText: string, aMarkers: LineMarker[], bText: string, bMarkers: LineMarker[], expectedText: string, expectedChangedMarkers: number[], _expectedMarkers: LineMarker[]): void {
+ function testLinePrependMarkers(aText: string, bText: string, expectedText: string): void {
let a = new ModelLine(aText, NO_TAB_SIZE);
- a.addMarkers(aMarkers);
-
let b = new ModelLine(bText, NO_TAB_SIZE);
- b.addMarkers(bMarkers);
- let changedMarkers = new MarkersTracker();
- a.append(changedMarkers, 1, b, NO_TAB_SIZE);
+ a.append(b, NO_TAB_SIZE);
assert.equal(a.text, expectedText, 'text');
-
- let actualMarkers = a.getMarkers().map(toLightWeightMarker);
- let expectedMarkers = _expectedMarkers.map(toLightWeightMarker);
- assert.deepEqual(actualMarkers, expectedMarkers, 'markers');
-
- let actualChangedMarkers = changedMarkers.getDecorationIds();
- actualChangedMarkers.sort();
- assert.deepEqual(actualChangedMarkers, expectedChangedMarkers, 'changed markers');
}
test('append to an empty', () => {
testLinePrependMarkers(
'abcd efgh',
- [
- markerOnFirstLine(1, 1, true),
- markerOnFirstLine(2, 1, false),
- markerOnFirstLine(3, 2, true),
- markerOnFirstLine(4, 2, false),
- markerOnFirstLine(5, 5, true),
- markerOnFirstLine(6, 5, false),
- markerOnFirstLine(7, 10, true),
- markerOnFirstLine(8, 10, false),
- ],
'',
- [
- ],
'abcd efgh',
- [],
- [
- markerOnFirstLine(1, 1, true),
- markerOnFirstLine(2, 1, false),
- markerOnFirstLine(3, 2, true),
- markerOnFirstLine(4, 2, false),
- markerOnFirstLine(5, 5, true),
- markerOnFirstLine(6, 5, false),
- markerOnFirstLine(7, 10, true),
- markerOnFirstLine(8, 10, false)
- ]
);
});
test('append an empty', () => {
testLinePrependMarkers(
'',
- [
- ],
'abcd efgh',
- [
- markerOnSecondLine(1, 1, true),
- markerOnSecondLine(2, 1, false),
- markerOnSecondLine(3, 2, true),
- markerOnSecondLine(4, 2, false),
- markerOnSecondLine(5, 5, true),
- markerOnSecondLine(6, 5, false),
- markerOnSecondLine(7, 10, true),
- markerOnSecondLine(8, 10, false),
- ],
'abcd efgh',
- [1, 2, 3, 4, 5, 6, 7, 8],
- [
- markerOnFirstLine(1, 1, true),
- markerOnFirstLine(2, 1, false),
- markerOnFirstLine(3, 2, true),
- markerOnFirstLine(4, 2, false),
- markerOnFirstLine(5, 5, true),
- markerOnFirstLine(6, 5, false),
- markerOnFirstLine(7, 10, true),
- markerOnFirstLine(8, 10, false)
- ]
);
});
test('append 1', () => {
testLinePrependMarkers(
'abcd',
- [
- markerOnFirstLine(1, 1, true),
- markerOnFirstLine(2, 1, false),
- markerOnFirstLine(3, 2, true),
- markerOnFirstLine(4, 2, false)
- ],
' efgh',
- [
- markerOnSecondLine(5, 1, true),
- markerOnSecondLine(6, 1, false),
- markerOnSecondLine(7, 6, true),
- markerOnSecondLine(8, 6, false),
- ],
'abcd efgh',
- [5, 6, 7, 8],
- [
- markerOnFirstLine(1, 1, true),
- markerOnFirstLine(2, 1, false),
- markerOnFirstLine(3, 2, true),
- markerOnFirstLine(4, 2, false),
- markerOnFirstLine(5, 5, true),
- markerOnFirstLine(6, 5, false),
- markerOnFirstLine(7, 10, true),
- markerOnFirstLine(8, 10, false)
- ]
);
});
test('append 2', () => {
testLinePrependMarkers(
'abcd e',
- [
- markerOnFirstLine(1, 1, true),
- markerOnFirstLine(2, 1, false),
- markerOnFirstLine(3, 2, true),
- markerOnFirstLine(4, 2, false),
- markerOnFirstLine(5, 5, true),
- markerOnFirstLine(6, 5, false)
- ],
'fgh',
- [
- markerOnSecondLine(7, 4, true),
- markerOnSecondLine(8, 4, false),
- ],
'abcd efgh',
- [7, 8],
- [
- markerOnFirstLine(1, 1, true),
- markerOnFirstLine(2, 1, false),
- markerOnFirstLine(3, 2, true),
- markerOnFirstLine(4, 2, false),
- markerOnFirstLine(5, 5, true),
- markerOnFirstLine(6, 5, false),
- markerOnFirstLine(7, 10, true),
- markerOnFirstLine(8, 10, false)
- ]
);
});
});
diff --git a/src/vs/editor/test/common/model/modelDecorations.test.ts b/src/vs/editor/test/common/model/modelDecorations.test.ts
index 1b2264090f0e6..ea402ff32c42f 100644
--- a/src/vs/editor/test/common/model/modelDecorations.test.ts
+++ b/src/vs/editor/test/common/model/modelDecorations.test.ts
@@ -8,7 +8,7 @@ import * as assert from 'assert';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
-import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/editorCommon';
+import { IModelDeltaDecoration, TrackedRangeStickiness, EndOfLineSequence } from 'vs/editor/common/editorCommon';
import { Model } from 'vs/editor/common/model/model';
// --------- utils
@@ -27,7 +27,8 @@ function modelHasDecorations(model: Model, decorations: ILightWeightDecoration2[
className: actualDecorations[i].options.className
});
}
- assert.deepEqual(modelDecorations, decorations, 'Model decorations');
+ modelDecorations.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
+ assert.deepEqual(modelDecorations, decorations);
}
function modelHasDecoration(model: Model, startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, className: string) {
@@ -168,13 +169,13 @@ suite('Editor Model - Model Decorations', () => {
var decId1 = addDecoration(thisModel, 1, 2, 3, 2, 'myType1');
var decId2 = addDecoration(thisModel, 1, 2, 3, 1, 'myType2');
modelHasDecorations(thisModel, [
- {
- range: new Range(1, 2, 3, 2),
- className: 'myType1'
- },
{
range: new Range(1, 2, 3, 1),
className: 'myType2'
+ },
+ {
+ range: new Range(1, 2, 3, 2),
+ className: 'myType1'
}
]);
thisModel.changeDecorations((changeAccessor) => {
@@ -207,9 +208,6 @@ suite('Editor Model - Model Decorations', () => {
let listenerCalled = 0;
thisModel.onDidChangeDecorations((e) => {
listenerCalled++;
- assert.equal(e.addedDecorations.length, 1);
- assert.equal(e.changedDecorations.length, 0);
- assert.equal(e.removedDecorations.length, 0);
});
addDecoration(thisModel, 1, 2, 3, 2, 'myType');
assert.equal(listenerCalled, 1, 'listener called');
@@ -220,10 +218,6 @@ suite('Editor Model - Model Decorations', () => {
let decId = addDecoration(thisModel, 1, 2, 3, 2, 'myType');
thisModel.onDidChangeDecorations((e) => {
listenerCalled++;
- assert.equal(e.addedDecorations.length, 0);
- assert.equal(e.changedDecorations.length, 1);
- assert.equal(e.changedDecorations[0], decId);
- assert.equal(e.removedDecorations.length, 0);
});
thisModel.changeDecorations((changeAccessor) => {
changeAccessor.changeDecoration(decId, new Range(1, 1, 1, 2));
@@ -236,10 +230,6 @@ suite('Editor Model - Model Decorations', () => {
let decId = addDecoration(thisModel, 1, 2, 3, 2, 'myType');
thisModel.onDidChangeDecorations((e) => {
listenerCalled++;
- assert.equal(e.addedDecorations.length, 0);
- assert.equal(e.changedDecorations.length, 0);
- assert.equal(e.removedDecorations.length, 1);
- assert.equal(e.removedDecorations[0], decId);
});
thisModel.changeDecorations((changeAccessor) => {
changeAccessor.removeDecoration(decId);
@@ -249,20 +239,31 @@ suite('Editor Model - Model Decorations', () => {
test('decorations emit event when inserting one line text before it', () => {
let listenerCalled = 0;
- let decId = addDecoration(thisModel, 1, 2, 3, 2, 'myType');
+ addDecoration(thisModel, 1, 2, 3, 2, 'myType');
thisModel.onDidChangeDecorations((e) => {
listenerCalled++;
- assert.equal(e.addedDecorations.length, 0);
- assert.equal(e.changedDecorations.length, 1);
- assert.equal(e.changedDecorations[0], decId);
- assert.equal(e.removedDecorations.length, 0);
});
thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'Hallo ')]);
assert.equal(listenerCalled, 1, 'listener called');
});
+ test('decorations do not emit event on no-op deltaDecorations', () => {
+ let listenerCalled = 0;
+
+ thisModel.onDidChangeDecorations((e) => {
+ listenerCalled++;
+ });
+
+ thisModel.deltaDecorations([], []);
+ thisModel.changeDecorations((accessor) => {
+ accessor.deltaDecorations([], []);
+ });
+
+ assert.equal(listenerCalled, 0, 'listener not called');
+ });
+
// --------- editing text & effects on decorations
test('decorations are updated when inserting one line text before it', () => {
@@ -365,6 +366,740 @@ suite('Editor Model - Model Decorations', () => {
thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 3, 1))]);
modelHasDecoration(thisModel, 1, 1, 2, 1, 'myType');
});
+
+ test('decorations are updated when changing EOL', () => {
+ addDecoration(thisModel, 1, 2, 4, 1, 'myType1');
+ addDecoration(thisModel, 1, 3, 4, 1, 'myType2');
+ addDecoration(thisModel, 1, 4, 4, 1, 'myType3');
+ addDecoration(thisModel, 1, 5, 4, 1, 'myType4');
+ addDecoration(thisModel, 1, 6, 4, 1, 'myType5');
+ addDecoration(thisModel, 1, 7, 4, 1, 'myType6');
+ addDecoration(thisModel, 1, 8, 4, 1, 'myType7');
+ addDecoration(thisModel, 1, 9, 4, 1, 'myType8');
+ addDecoration(thisModel, 1, 10, 4, 1, 'myType9');
+ thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'x')]);
+ thisModel.setEOL(EndOfLineSequence.CRLF);
+ thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'x')]);
+ modelHasDecorations(thisModel, [
+ { range: new Range(1, 4, 4, 1), className: 'myType1' },
+ { range: new Range(1, 5, 4, 1), className: 'myType2' },
+ { range: new Range(1, 6, 4, 1), className: 'myType3' },
+ { range: new Range(1, 7, 4, 1), className: 'myType4' },
+ { range: new Range(1, 8, 4, 1), className: 'myType5' },
+ { range: new Range(1, 9, 4, 1), className: 'myType6' },
+ { range: new Range(1, 10, 4, 1), className: 'myType7' },
+ { range: new Range(1, 11, 4, 1), className: 'myType8' },
+ { range: new Range(1, 12, 4, 1), className: 'myType9' },
+ ]);
+ });
+
+ test('an apparently simple edit', () => {
+ addDecoration(thisModel, 1, 2, 4, 1, 'myType1');
+ thisModel.applyEdits([EditOperation.replace(new Range(1, 14, 2, 1), 'x')]);
+ modelHasDecorations(thisModel, [
+ { range: new Range(1, 2, 3, 1), className: 'myType1' },
+ ]);
+ });
+
+ test('removeAllDecorationsWithOwnerId can be called after model dispose', () => {
+ let model = Model.createFromString('asd');
+ model.dispose();
+ model.removeAllDecorationsWithOwnerId(1);
+ });
+
+ test('removeAllDecorationsWithOwnerId works', () => {
+ thisModel.deltaDecorations([], [{ range: new Range(1, 2, 4, 1), options: { className: 'myType1' } }], 1);
+ thisModel.removeAllDecorationsWithOwnerId(1);
+ modelHasNoDecorations(thisModel);
+ });
+});
+
+suite('Decorations and editing', () => {
+
+ function _runTest(decRange: Range, stickiness: TrackedRangeStickiness, editRange: Range, editText: string, editForceMoveMarkers: boolean, expectedDecRange: Range, msg: string): void {
+ let model = Model.createFromString([
+ 'My First Line',
+ 'My Second Line',
+ 'Third Line'
+ ].join('\n'));
+
+ const id = model.deltaDecorations([], [{ range: decRange, options: { stickiness: stickiness } }])[0];
+ model.applyEdits([{ range: editRange, text: editText, forceMoveMarkers: editForceMoveMarkers, identifier: null }]);
+ const actual = model.getDecorationRange(id);
+ assert.deepEqual(actual, expectedDecRange, msg);
+
+ model.dispose();
+ }
+
+ function runTest(decRange: Range, editRange: Range, editText: string, expectedDecRange: Range[][]): void {
+ _runTest(decRange, 0, editRange, editText, false, expectedDecRange[0][0], 'no-0-AlwaysGrowsWhenTypingAtEdges');
+ _runTest(decRange, 1, editRange, editText, false, expectedDecRange[0][1], 'no-1-NeverGrowsWhenTypingAtEdges');
+ _runTest(decRange, 2, editRange, editText, false, expectedDecRange[0][2], 'no-2-GrowsOnlyWhenTypingBefore');
+ _runTest(decRange, 3, editRange, editText, false, expectedDecRange[0][3], 'no-3-GrowsOnlyWhenTypingAfter');
+
+ _runTest(decRange, 0, editRange, editText, true, expectedDecRange[1][0], 'force-0-AlwaysGrowsWhenTypingAtEdges');
+ _runTest(decRange, 1, editRange, editText, true, expectedDecRange[1][1], 'force-1-NeverGrowsWhenTypingAtEdges');
+ _runTest(decRange, 2, editRange, editText, true, expectedDecRange[1][2], 'force-2-GrowsOnlyWhenTypingBefore');
+ _runTest(decRange, 3, editRange, editText, true, expectedDecRange[1][3], 'force-3-GrowsOnlyWhenTypingAfter');
+ }
+
+ suite('insert', () => {
+ suite('collapsed dec', () => {
+ test('before', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 3), 'xx',
+ [
+ [new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6)],
+ [new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('equal', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 4), 'xx',
+ [
+ [new Range(1, 4, 1, 6), new Range(1, 6, 1, 6), new Range(1, 4, 1, 4), new Range(1, 6, 1, 6)],
+ [new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('after', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 5), 'xx',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('before', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 3), 'xx',
+ [
+ [new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11)],
+ [new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11)],
+ ]
+ );
+ });
+ test('start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 4), 'xx',
+ [
+ [new Range(1, 4, 1, 11), new Range(1, 6, 1, 11), new Range(1, 4, 1, 11), new Range(1, 6, 1, 11)],
+ [new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11)],
+ ]
+ );
+ });
+ test('inside', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 5), 'xx',
+ [
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11)],
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11)],
+ ]
+ );
+ });
+ test('end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 9), 'xx',
+ [
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 11)],
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11)],
+ ]
+ );
+ });
+ test('after', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 10), 'xx',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ ]
+ );
+ });
+ });
+ });
+
+ suite('delete', () => {
+ suite('collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 1, 1, 3), '',
+ [
+ [new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2)],
+ [new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 2, 1, 4), '',
+ [
+ [new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2)],
+ [new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2), new Range(1, 2, 1, 2)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 5), '',
+ [
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ ]
+ );
+ });
+ test('edit.start >= range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 6), '',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 7), '',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 1, 1, 3), '',
+ [
+ [new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7)],
+ [new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 2, 1, 4), '',
+ [
+ [new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7)],
+ [new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7), new Range(1, 2, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 5), '',
+ [
+ [new Range(1, 3, 1, 7), new Range(1, 3, 1, 7), new Range(1, 3, 1, 7), new Range(1, 3, 1, 7)],
+ [new Range(1, 3, 1, 7), new Range(1, 3, 1, 7), new Range(1, 3, 1, 7), new Range(1, 3, 1, 7)],
+ ]
+ );
+ });
+
+ test('edit.start < range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 9), '',
+ [
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ ]
+ );
+ });
+
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 10), '',
+ [
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ ]
+ );
+ });
+
+ test('edit.start == range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 6), '',
+ [
+ [new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7)],
+ [new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7)],
+ ]
+ );
+ });
+
+ test('edit.start == range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 9), '',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start == range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 10), '',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+
+ test('edit.start > range.start && edit.start < range.end && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 7), '',
+ [
+ [new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7)],
+ [new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7)],
+ ]
+ );
+ });
+
+ test('edit.start > range.start && edit.start < range.end && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 9), '',
+ [
+ [new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5)],
+ [new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5)],
+ ]
+ );
+ });
+
+ test('edit.start > range.start && edit.start < range.end && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 10), '',
+ [
+ [new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5)],
+ [new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5)],
+ ]
+ );
+ });
+
+ test('edit.start == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 11), '',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ ]
+ );
+ });
+
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 11), '',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ ]
+ );
+ });
+ });
+ });
+
+ suite('replace short', () => {
+ suite('collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 1, 1, 3), 'c',
+ [
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 2, 1, 4), 'c',
+ [
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ [new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3), new Range(1, 3, 1, 3)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 5), 'c',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start >= range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 6), 'c',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 5, 1, 5), new Range(1, 5, 1, 5), new Range(1, 5, 1, 5), new Range(1, 5, 1, 5)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 7), 'c',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 1, 1, 3), 'c',
+ [
+ [new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8)],
+ [new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 2, 1, 4), 'c',
+ [
+ [new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8)],
+ [new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8), new Range(1, 3, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 5), 'c',
+ [
+ [new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8)],
+ [new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 9), 'c',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 10), 'c',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 6), 'c',
+ [
+ [new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8)],
+ [new Range(1, 5, 1, 8), new Range(1, 5, 1, 8), new Range(1, 5, 1, 8), new Range(1, 5, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 9), 'c',
+ [
+ [new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5)],
+ [new Range(1, 5, 1, 5), new Range(1, 5, 1, 5), new Range(1, 5, 1, 5), new Range(1, 5, 1, 5)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 10), 'c',
+ [
+ [new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5), new Range(1, 4, 1, 5)],
+ [new Range(1, 5, 1, 5), new Range(1, 5, 1, 5), new Range(1, 5, 1, 5), new Range(1, 5, 1, 5)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 7), 'c',
+ [
+ [new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8)],
+ [new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 9), 'c',
+ [
+ [new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6)],
+ [new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 10), 'c',
+ [
+ [new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6)],
+ [new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6), new Range(1, 4, 1, 6)],
+ ]
+ );
+ });
+ test('edit.start == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 11), 'c',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 10), new Range(1, 4, 1, 10), new Range(1, 4, 1, 10), new Range(1, 4, 1, 10)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 11), 'c',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ ]
+ );
+ });
+ });
+ });
+
+ suite('replace long', () => {
+ suite('collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 1, 1, 3), 'cccc',
+ [
+ [new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6)],
+ [new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 2, 1, 4), 'cccc',
+ [
+ [new Range(1, 4, 1, 6), new Range(1, 6, 1, 6), new Range(1, 4, 1, 4), new Range(1, 6, 1, 6)],
+ [new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6), new Range(1, 6, 1, 6)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 3, 1, 5), 'cccc',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 7, 1, 7), new Range(1, 7, 1, 7), new Range(1, 7, 1, 7), new Range(1, 7, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start >= range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 4, 1, 6), 'cccc',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 8, 1, 8), new Range(1, 8, 1, 8), new Range(1, 8, 1, 8), new Range(1, 8, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 4),
+ new Range(1, 5, 1, 7), 'cccc',
+ [
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ [new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4), new Range(1, 4, 1, 4)],
+ ]
+ );
+ });
+ });
+ suite('non-collapsed dec', () => {
+ test('edit.end < range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 1, 1, 3), 'cccc',
+ [
+ [new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11)],
+ [new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11)],
+ ]
+ );
+ });
+ test('edit.end <= range.start', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 2, 1, 4), 'cccc',
+ [
+ [new Range(1, 4, 1, 11), new Range(1, 6, 1, 11), new Range(1, 4, 1, 11), new Range(1, 6, 1, 11)],
+ [new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11), new Range(1, 6, 1, 11)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 5), 'cccc',
+ [
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11)],
+ [new Range(1, 7, 1, 11), new Range(1, 7, 1, 11), new Range(1, 7, 1, 11), new Range(1, 7, 1, 11)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 9), 'cccc',
+ [
+ [new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7)],
+ [new Range(1, 7, 1, 7), new Range(1, 7, 1, 7), new Range(1, 7, 1, 7), new Range(1, 7, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start < range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 3, 1, 10), 'cccc',
+ [
+ [new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7), new Range(1, 4, 1, 7)],
+ [new Range(1, 7, 1, 7), new Range(1, 7, 1, 7), new Range(1, 7, 1, 7), new Range(1, 7, 1, 7)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 6), 'cccc',
+ [
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11)],
+ [new Range(1, 8, 1, 11), new Range(1, 8, 1, 11), new Range(1, 8, 1, 11), new Range(1, 8, 1, 11)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 9), 'cccc',
+ [
+ [new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8)],
+ [new Range(1, 8, 1, 8), new Range(1, 8, 1, 8), new Range(1, 8, 1, 8), new Range(1, 8, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start == range.start && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 4, 1, 10), 'cccc',
+ [
+ [new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8), new Range(1, 4, 1, 8)],
+ [new Range(1, 8, 1, 8), new Range(1, 8, 1, 8), new Range(1, 8, 1, 8), new Range(1, 8, 1, 8)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end < range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 7), 'cccc',
+ [
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11)],
+ [new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11), new Range(1, 4, 1, 11)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 9), 'cccc',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ ]
+ );
+ });
+ test('edit.start > range.start && edit.start < range.end && edit.end > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 5, 1, 10), 'cccc',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ ]
+ );
+ });
+ test('edit.start == range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 9, 1, 11), 'cccc',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 13), new Range(1, 4, 1, 13), new Range(1, 4, 1, 13), new Range(1, 4, 1, 13)],
+ ]
+ );
+ });
+ test('edit.start > range.end', () => {
+ runTest(
+ new Range(1, 4, 1, 9),
+ new Range(1, 10, 1, 11), 'cccc',
+ [
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ [new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9), new Range(1, 4, 1, 9)],
+ ]
+ );
+ });
+ });
+ });
});
interface ILightWeightDecoration {
@@ -420,7 +1155,6 @@ suite('deltaDecorations', () => {
assert.equal(initialIds.length, decorations.length, 'returns expected cnt of ids');
assert.equal(initialIds.length, model.getAllDecorations().length, 'does not leak decorations');
assert.equal(initialIds.length, model._getTrackedRangesCount(), 'does not leak tracked ranges');
- assert.equal(2 * initialIds.length, model._getMarkersCount(), 'does not leak markers');
actualDecorations.sort((a, b) => strcmp(a.id, b.id));
decorations.sort((a, b) => strcmp(a.id, b.id));
assert.deepEqual(actualDecorations, decorations);
@@ -431,7 +1165,6 @@ suite('deltaDecorations', () => {
assert.equal(newIds.length, newDecorations.length, 'returns expected cnt of ids');
assert.equal(newIds.length, model.getAllDecorations().length, 'does not leak decorations');
assert.equal(newIds.length, model._getTrackedRangesCount(), 'does not leak tracked ranges');
- assert.equal(2 * newIds.length, model._getMarkersCount(), 'does not leak markers');
actualNewDecorations.sort((a, b) => strcmp(a.id, b.id));
newDecorations.sort((a, b) => strcmp(a.id, b.id));
assert.deepEqual(actualDecorations, decorations);
diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts
index 0f52c5129c333..20bbda92be7e8 100644
--- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts
+++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts
@@ -91,23 +91,23 @@ suite('ViewModelDecorations', () => {
let actualDecorations = viewModel.getDecorationsInViewport(
new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3))
).map((dec) => {
- return dec.source.id;
+ return dec.options.className;
});
assert.deepEqual(actualDecorations, [
- dec2,
- dec3,
- dec4,
- dec5,
- dec6,
- dec7,
- dec8,
- dec9,
- dec10,
- dec11,
- dec12,
- dec13,
- dec14,
+ 'dec2',
+ 'dec3',
+ 'dec4',
+ 'dec5',
+ 'dec6',
+ 'dec7',
+ 'dec8',
+ 'dec9',
+ 'dec10',
+ 'dec11',
+ 'dec12',
+ 'dec13',
+ 'dec14',
]);
let inlineDecorations1 = viewModel.getViewLineRenderingData(
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
index 8470c6ce84189..9142f59537b36 100644
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -1196,10 +1196,6 @@ declare module monaco.editor {
* Options associated with this decoration.
*/
readonly options: IModelDecorationOptions;
- /**
- * A flag describing if this is a problem decoration (e.g. warning/error).
- */
- readonly isForValidation: boolean;
}
/**
@@ -1649,12 +1645,6 @@ declare module monaco.editor {
getWordUntilPosition(position: IPosition): IWordAtPosition;
}
- /**
- * A model that can track markers.
- */
- export interface ITextModelWithMarkers extends ITextModel {
- }
-
/**
* Describes the behavior of decorations when typing/editing near their edges.
* Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior`
@@ -1725,12 +1715,18 @@ declare module monaco.editor {
* @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors).
*/
getAllDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[];
+ /**
+ * Gets all the decorations that should be rendered in the overview ruler as an array.
+ * @param ownerId If set, it will ignore decorations belonging to other owners.
+ * @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors).
+ */
+ getOverviewRulerDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[];
}
/**
* An editable text model.
*/
- export interface IEditableTextModel extends ITextModelWithMarkers {
+ export interface IEditableTextModel extends ITextModel {
/**
* Normalize a string containing whitespace according to indentation rules (converts to spaces or to tabs).
*/
@@ -1774,7 +1770,7 @@ declare module monaco.editor {
/**
* A model.
*/
- export interface IModel extends IReadOnlyModel, IEditableTextModel, ITextModelWithMarkers, ITokenizedModel, ITextModelWithDecorations {
+ export interface IModel extends IReadOnlyModel, IEditableTextModel, ITokenizedModel, ITextModelWithDecorations {
/**
* An event emitted when the contents of the model have changed.
* @event
@@ -2490,18 +2486,6 @@ declare module monaco.editor {
* An event describing that model decorations have changed.
*/
export interface IModelDecorationsChangedEvent {
- /**
- * Lists of ids for added decorations.
- */
- readonly addedDecorations: string[];
- /**
- * Lists of ids for changed decorations.
- */
- readonly changedDecorations: string[];
- /**
- * List of ids for removed decorations.
- */
- readonly removedDecorations: string[];
}
/**
diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts b/src/vs/workbench/api/electron-browser/mainThreadEditor.ts
index 0da01725b1756..bc23649dbe8ce 100644
--- a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts
+++ b/src/vs/workbench/api/electron-browser/mainThreadEditor.ts
@@ -244,6 +244,17 @@ export class MainThreadTextEditor {
this._codeEditor.setDecorations(key, ranges);
}
+ public setDecorationsFast(key: string, _ranges: number[]): void {
+ if (!this._codeEditor) {
+ return;
+ }
+ let ranges: Range[] = [];
+ for (let i = 0, len = Math.floor(_ranges.length / 4); i < len; i++) {
+ ranges[i] = new Range(_ranges[4 * i], _ranges[4 * i + 1], _ranges[4 * i + 2], _ranges[4 * i + 3]);
+ }
+ this._codeEditor.setDecorationsFast(key, ranges);
+ }
+
public revealRange(range: IRange, revealType: TextEditorRevealType): void {
if (!this._codeEditor) {
return;
diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts
index df3174ae67cec..6787006461b0c 100644
--- a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts
+++ b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts
@@ -195,6 +195,14 @@ export class MainThreadEditors implements MainThreadEditorsShape {
return TPromise.as(null);
}
+ $trySetDecorationsFast(id: string, key: string, ranges: string): TPromise {
+ if (!this._documentsAndEditors.getEditor(id)) {
+ return TPromise.wrapError(disposed(`TextEditor(${id})`));
+ }
+ this._documentsAndEditors.getEditor(id).setDecorationsFast(key, /*TODO: marshaller is too slow*/JSON.parse(ranges));
+ return TPromise.as(null);
+ }
+
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise {
if (!this._documentsAndEditors.getEditor(id)) {
return TPromise.wrapError(disposed(`TextEditor(${id})`));
diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts
index a6c002ae30fc8..2619ab29fcb04 100644
--- a/src/vs/workbench/api/node/extHost.protocol.ts
+++ b/src/vs/workbench/api/node/extHost.protocol.ts
@@ -220,6 +220,7 @@ export interface MainThreadEditorsShape extends IDisposable {
$tryHideEditor(id: string): TPromise;
$trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise;
$trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): TPromise;
+ $trySetDecorationsFast(id: string, key: string, ranges: string): TPromise;
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise;
$trySetSelections(id: string, selections: ISelection[]): TPromise;
$tryApplyEdits(id: string, modelVersionId: number, edits: editorCommon.ISingleEditOperation[], opts: IApplyEditsOptions): TPromise;
diff --git a/src/vs/workbench/api/node/extHostTextEditor.ts b/src/vs/workbench/api/node/extHostTextEditor.ts
index 763969acc9696..9872ac721fdaa 100644
--- a/src/vs/workbench/api/node/extHostTextEditor.ts
+++ b/src/vs/workbench/api/node/extHostTextEditor.ts
@@ -416,11 +416,29 @@ export class ExtHostTextEditor implements vscode.TextEditor {
setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void {
this._runOnProxy(
- () => this._proxy.$trySetDecorations(
- this._id,
- decorationType.key,
- TypeConverters.fromRangeOrRangeWithMessage(ranges)
- )
+ () => {
+ if (TypeConverters.isDecorationOptionsArr(ranges)) {
+ return this._proxy.$trySetDecorations(
+ this._id,
+ decorationType.key,
+ TypeConverters.fromRangeOrRangeWithMessage(ranges)
+ );
+ } else {
+ let _ranges: number[] = new Array(4 * ranges.length);
+ for (let i = 0, len = ranges.length; i < len; i++) {
+ const range = ranges[i];
+ _ranges[4 * i] = range.start.line + 1;
+ _ranges[4 * i + 1] = range.start.character + 1;
+ _ranges[4 * i + 2] = range.end.line + 1;
+ _ranges[4 * i + 3] = range.end.character + 1;
+ }
+ return this._proxy.$trySetDecorationsFast(
+ this._id,
+ decorationType.key,
+ /*TODO: marshaller is too slow*/JSON.stringify(_ranges)
+ );
+ }
+ }
);
}
diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts
index 54c80ba84c46d..ca9561d8d251f 100644
--- a/src/vs/workbench/api/node/extHostTypeConverters.ts
+++ b/src/vs/workbench/api/node/extHostTypeConverters.ts
@@ -139,7 +139,7 @@ function isDecorationOptions(something: any): something is vscode.DecorationOpti
return (typeof something.range !== 'undefined');
}
-function isDecorationOptionsArr(something: vscode.Range[] | vscode.DecorationOptions[]): something is vscode.DecorationOptions[] {
+export function isDecorationOptionsArr(something: vscode.Range[] | vscode.DecorationOptions[]): something is vscode.DecorationOptions[] {
if (something.length === 0) {
return true;
}
diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts
index 6fd5d7c1c2031..55f5e51c46fa8 100644
--- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts
+++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts
@@ -13,7 +13,6 @@ import { IModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecoration
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDebugService, IBreakpoint, IRawBreakpoint, State } from 'vs/workbench/parts/debug/common/debug';
import { IModelService } from 'vs/editor/common/services/modelService';
-import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { MarkdownString } from 'vs/base/common/htmlContent';
interface IDebugEditorModelData {
@@ -21,7 +20,7 @@ interface IDebugEditorModelData {
toDispose: lifecycle.IDisposable[];
breakpointDecorationIds: string[];
breakpointLines: number[];
- breakpointDecorationsAsMap: Map;
+ breakpointDecorationsAsMap: Map;
currentStackDecorations: string[];
dirty: boolean;
topStackFrameRange: Range;
@@ -81,11 +80,12 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp.uri.toString() === modelUrlStr);
const currentStackDecorations = model.deltaDecorations([], this.createCallStackDecorations(modelUrlStr));
- const breakPointDecorations = model.deltaDecorations([], this.createBreakpointDecorations(breakpoints));
+ const desiredDecorations = this.createBreakpointDecorations(model, breakpoints);
+ const breakPointDecorations = model.deltaDecorations([], desiredDecorations);
- const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUrlStr, e))];
- const breakpointDecorationsAsMap = new Map();
- breakPointDecorations.forEach(bpd => breakpointDecorationsAsMap.set(bpd, true));
+ const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUrlStr))];
+ const breakpointDecorationsAsMap = new Map();
+ breakPointDecorations.forEach((decorationId, index) => breakpointDecorationsAsMap.set(decorationId, desiredDecorations[index].range));
this.modelDataMap.set(modelUrlStr, {
model: model,
@@ -185,13 +185,23 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
}
// breakpoints management. Represent data coming from the debug service and also send data back.
- private onModelDecorationsChanged(modelUrlStr: string, e: IModelDecorationsChangedEvent): void {
+ private onModelDecorationsChanged(modelUrlStr: string): void {
const modelData = this.modelDataMap.get(modelUrlStr);
if (modelData.breakpointDecorationsAsMap.size === 0) {
// I have no decorations
return;
}
- if (!e.changedDecorations.some(decorationId => modelData.breakpointDecorationsAsMap.has(decorationId))) {
+ let somethingChanged = false;
+ modelData.breakpointDecorationsAsMap.forEach((breakpointRange, decorationId) => {
+ if (somethingChanged) {
+ return;
+ }
+ const newBreakpointRange = modelData.model.getDecorationRange(decorationId);
+ if (newBreakpointRange && !breakpointRange.equalsRange(newBreakpointRange)) {
+ somethingChanged = true;
+ }
+ });
+ if (!somethingChanged) {
// nothing to do, my decorations did not change.
return;
}
@@ -254,16 +264,19 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
}
private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void {
- modelData.breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorationIds, this.createBreakpointDecorations(newBreakpoints));
+ const desiredDecorations = this.createBreakpointDecorations(modelData.model, newBreakpoints);
+ modelData.breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorationIds, desiredDecorations);
modelData.breakpointDecorationsAsMap.clear();
- modelData.breakpointDecorationIds.forEach(id => modelData.breakpointDecorationsAsMap.set(id, true));
+ modelData.breakpointDecorationIds.forEach((decorationId, index) => modelData.breakpointDecorationsAsMap.set(decorationId, desiredDecorations[index].range));
modelData.breakpointLines = newBreakpoints.map(bp => bp.lineNumber);
}
- private createBreakpointDecorations(breakpoints: IBreakpoint[]): IModelDeltaDecoration[] {
+ private createBreakpointDecorations(model: IModel, breakpoints: IBreakpoint[]): { range: Range; options: IModelDecorationOptions; }[] {
return breakpoints.map((breakpoint) => {
- const range = breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1)
- : new Range(breakpoint.lineNumber, 1, breakpoint.lineNumber, Constants.MAX_SAFE_SMALL_INTEGER); // Decoration has to have a width #20688
+ const range = model.validateRange(
+ breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1)
+ : new Range(breakpoint.lineNumber, 1, breakpoint.lineNumber, Constants.MAX_SAFE_SMALL_INTEGER) // Decoration has to have a width #20688
+ );
return {
options: this.getBreakpointDecorationOptions(breakpoint),
range
diff --git a/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts
index 402466a620e12..48d6e46c0c71d 100644
--- a/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts
+++ b/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts
@@ -61,6 +61,7 @@ suite('ExtHostTextEditorOptions', () => {
$tryShowEditor: undefined,
$tryHideEditor: undefined,
$trySetDecorations: undefined,
+ $trySetDecorationsFast: undefined,
$tryRevealRange: undefined,
$trySetSelections: undefined,
$tryApplyEdits: undefined,