SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,204 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { InlineDecoration } from 'vs/editor/common/viewModel/viewModel';
import { Constants } from 'vs/editor/common/core/uint';
export class LineDecoration {
_lineDecorationBrand: void;
public readonly startColumn: number;
public readonly endColumn: number;
public readonly className: string;
public readonly insertsBeforeOrAfter: boolean;
constructor(startColumn: number, endColumn: number, className: string, insertsBeforeOrAfter: boolean) {
this.startColumn = startColumn;
this.endColumn = endColumn;
this.className = className;
this.insertsBeforeOrAfter = insertsBeforeOrAfter;
}
private static _equals(a: LineDecoration, b: LineDecoration): boolean {
return (
a.startColumn === b.startColumn
&& a.endColumn === b.endColumn
&& a.className === b.className
&& a.insertsBeforeOrAfter === b.insertsBeforeOrAfter
);
}
public static equalsArr(a: LineDecoration[], b: LineDecoration[]): boolean {
let aLen = a.length;
let bLen = b.length;
if (aLen !== bLen) {
return false;
}
for (let i = 0; i < aLen; i++) {
if (!LineDecoration._equals(a[i], b[i])) {
return false;
}
}
return true;
}
public static filter(lineDecorations: InlineDecoration[], lineNumber: number, minLineColumn: number, maxLineColumn: number): LineDecoration[] {
if (lineDecorations.length === 0) {
return [];
}
let result: LineDecoration[] = [], resultLen = 0;
for (let i = 0, len = lineDecorations.length; i < len; i++) {
let d = lineDecorations[i];
let range = d.range;
if (range.endLineNumber < lineNumber || range.startLineNumber > lineNumber) {
// Ignore decorations that sit outside this line
continue;
}
if (range.isEmpty()) {
// Ignore empty range decorations
continue;
}
let startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn);
let endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn);
if (endColumn <= 1) {
// An empty decoration (endColumn === 1)
continue;
}
result[resultLen++] = new LineDecoration(startColumn, endColumn, d.inlineClassName, d.insertsBeforeOrAfter);
}
return result;
}
public static compare(a: LineDecoration, b: LineDecoration): number {
if (a.startColumn === b.startColumn) {
if (a.endColumn === b.endColumn) {
if (a.className < b.className) {
return -1;
}
if (a.className > b.className) {
return 1;
}
return 0;
}
return a.endColumn - b.endColumn;
}
return a.startColumn - b.startColumn;
}
}
export class DecorationSegment {
startOffset: number;
endOffset: number;
className: string;
constructor(startOffset: number, endOffset: number, className: string) {
this.startOffset = startOffset;
this.endOffset = endOffset;
this.className = className;
}
}
class Stack {
public count: number;
private stopOffsets: number[];
private classNames: string[];
constructor() {
this.stopOffsets = [];
this.classNames = [];
this.count = 0;
}
public consumeLowerThan(maxStopOffset: number, nextStartOffset: number, result: DecorationSegment[]): number {
while (this.count > 0 && this.stopOffsets[0] < maxStopOffset) {
var i = 0;
// Take all equal stopping offsets
while (i + 1 < this.count && this.stopOffsets[i] === this.stopOffsets[i + 1]) {
i++;
}
// Basically we are consuming the first i + 1 elements of the stack
result.push(new DecorationSegment(nextStartOffset, this.stopOffsets[i], this.classNames.join(' ')));
nextStartOffset = this.stopOffsets[i] + 1;
// Consume them
this.stopOffsets.splice(0, i + 1);
this.classNames.splice(0, i + 1);
this.count -= (i + 1);
}
if (this.count > 0 && nextStartOffset < maxStopOffset) {
result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' ')));
nextStartOffset = maxStopOffset;
}
return nextStartOffset;
}
public insert(stopOffset: number, className: string): void {
if (this.count === 0 || this.stopOffsets[this.count - 1] <= stopOffset) {
// Insert at the end
this.stopOffsets.push(stopOffset);
this.classNames.push(className);
} else {
// Find the insertion position for `stopOffset`
for (var i = 0; i < this.count; i++) {
if (this.stopOffsets[i] >= stopOffset) {
this.stopOffsets.splice(i, 0, stopOffset);
this.classNames.splice(i, 0, className);
break;
}
}
}
this.count++;
return;
}
}
export class LineDecorationsNormalizer {
/**
* Normalize line decorations. Overlapping decorations will generate multiple segments
*/
public static normalize(lineDecorations: LineDecoration[]): DecorationSegment[] {
if (lineDecorations.length === 0) {
return [];
}
let result: DecorationSegment[] = [];
let stack = new Stack();
let nextStartOffset = 0;
for (let i = 0, len = lineDecorations.length; i < len; i++) {
let d = lineDecorations[i];
let currentStartOffset = d.startColumn - 1;
let currentEndOffset = d.endColumn - 2;
nextStartOffset = stack.consumeLowerThan(currentStartOffset, nextStartOffset, result);
if (stack.count === 0) {
nextStartOffset = currentStartOffset;
}
stack.insert(currentEndOffset, d.className);
}
stack.consumeLowerThan(Constants.MAX_SAFE_SMALL_INTEGER, nextStartOffset, result);
return result;
}
}

View File

@@ -0,0 +1,475 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { WhitespaceComputer, IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
/**
* Layouting of objects that take vertical space (by having a height) and push down other objects.
*
* These objects are basically either text (lines) or spaces between those lines (whitespaces).
* This provides commodity operations for working with lines that contain whitespace that pushes lines lower (vertically).
* This is written with no knowledge of an editor in mind.
*/
export class LinesLayout {
/**
* Keep track of the total number of lines.
* This is useful for doing binary searches or for doing hit-testing.
*/
private _lineCount: number;
/**
* The height of a line in pixels.
*/
private _lineHeight: number;
/**
* Contains whitespace information in pixels
*/
private _whitespaces: WhitespaceComputer;
constructor(lineCount: number, lineHeight: number) {
this._lineCount = lineCount;
this._lineHeight = lineHeight;
this._whitespaces = new WhitespaceComputer();
}
/**
* Change the height of a line in pixels.
*/
public setLineHeight(lineHeight: number): void {
this._lineHeight = lineHeight;
}
/**
* Set the number of lines.
*
* @param lineCount New number of lines.
*/
public onFlushed(lineCount: number): void {
this._lineCount = lineCount;
}
/**
* Insert a new whitespace of a certain height after a line number.
* The whitespace has a "sticky" characteristic.
* Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line.
*
* @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below.
* @param heightInPx The height of the whitespace, in pixels.
* @return An id that can be used later to mutate or delete the whitespace
*/
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number): number {
return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx);
}
/**
* Change properties associated with a certain whitespace.
*/
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight);
}
/**
* Remove an existing whitespace.
*
* @param id The whitespace to remove
* @return Returns true if the whitespace is found and it is removed.
*/
public removeWhitespace(id: number): boolean {
return this._whitespaces.removeWhitespace(id);
}
/**
* Notify the layouter that lines have been deleted (a continuous zone of lines).
*
* @param fromLineNumber The line number at which the deletion started, inclusive
* @param toLineNumber The line number at which the deletion ended, inclusive
*/
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
this._lineCount -= (toLineNumber - fromLineNumber + 1);
this._whitespaces.onLinesDeleted(fromLineNumber, toLineNumber);
}
/**
* Notify the layouter that lines have been inserted (a continuous zone of lines).
*
* @param fromLineNumber The line number at which the insertion started, inclusive
* @param toLineNumber The line number at which the insertion ended, inclusive.
*/
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
this._lineCount += (toLineNumber - fromLineNumber + 1);
this._whitespaces.onLinesInserted(fromLineNumber, toLineNumber);
}
/**
* Get the sum of heights for all objects.
*
* @return The sum of heights for all objects.
*/
public getLinesTotalHeight(): number {
let linesHeight = this._lineHeight * this._lineCount;
let whitespacesHeight = this._whitespaces.getTotalHeight();
return linesHeight + whitespacesHeight;
}
/**
* Get the vertical offset (the sum of heights for all objects above) a certain line number.
*
* @param lineNumber The line number
* @return The sum of heights for all objects above `lineNumber`.
*/
public getVerticalOffsetForLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
let previousLinesHeight: number;
if (lineNumber > 1) {
previousLinesHeight = this._lineHeight * (lineNumber - 1);
} else {
previousLinesHeight = 0;
}
let previousWhitespacesHeight = this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber);
return previousLinesHeight + previousWhitespacesHeight;
}
/**
* Returns the accumulated height of whitespaces before the given line number.
*
* @param lineNumber The line number
*/
public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
return this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber);
}
/**
* Returns if there is any whitespace in the document.
*/
public hasWhitespace(): boolean {
return this._whitespaces.getCount() > 0;
}
/**
* Check if `verticalOffset` is below all lines.
*/
public isAfterLines(verticalOffset: number): boolean {
let totalHeight = this.getLinesTotalHeight();
return verticalOffset > totalHeight;
}
/**
* Find the first line number that is at or after vertical offset `verticalOffset`.
* i.e. if getVerticalOffsetForLine(line) is x and getVerticalOffsetForLine(line + 1) is y, then
* getLineNumberAtOrAfterVerticalOffset(i) = line, x <= i < y.
*
* @param verticalOffset The vertical offset to search at.
* @return The line number at or after vertical offset `verticalOffset`.
*/
public getLineNumberAtOrAfterVerticalOffset(verticalOffset: number): number {
verticalOffset = verticalOffset | 0;
if (verticalOffset < 0) {
return 1;
}
const linesCount = this._lineCount | 0;
const lineHeight = this._lineHeight;
let minLineNumber = 1;
let maxLineNumber = linesCount;
while (minLineNumber < maxLineNumber) {
let midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0;
let midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0;
if (verticalOffset >= midLineNumberVerticalOffset + lineHeight) {
// vertical offset is after mid line number
minLineNumber = midLineNumber + 1;
} else if (verticalOffset >= midLineNumberVerticalOffset) {
// Hit
return midLineNumber;
} else {
// vertical offset is before mid line number, but mid line number could still be what we're searching for
maxLineNumber = midLineNumber;
}
}
if (minLineNumber > linesCount) {
return linesCount;
}
return minLineNumber;
}
/**
* Get all the lines and their relative vertical offsets that are positioned between `verticalOffset1` and `verticalOffset2`.
*
* @param verticalOffset1 The beginning of the viewport.
* @param verticalOffset2 The end of the viewport.
* @return A structure describing the lines positioned between `verticalOffset1` and `verticalOffset2`.
*/
public getLinesViewportData(verticalOffset1: number, verticalOffset2: number): IPartialViewLinesViewportData {
verticalOffset1 = verticalOffset1 | 0;
verticalOffset2 = verticalOffset2 | 0;
const lineHeight = this._lineHeight;
// Find first line number
// We don't live in a perfect world, so the line number might start before or after verticalOffset1
const startLineNumber = this.getLineNumberAtOrAfterVerticalOffset(verticalOffset1) | 0;
const startLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(startLineNumber) | 0;
let endLineNumber = this._lineCount | 0;
// Also keep track of what whitespace we've got
let whitespaceIndex = this._whitespaces.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0;
const whitespaceCount = this._whitespaces.getCount() | 0;
let currentWhitespaceHeight: number;
let currentWhitespaceAfterLineNumber: number;
if (whitespaceIndex === -1) {
whitespaceIndex = whitespaceCount;
currentWhitespaceAfterLineNumber = endLineNumber + 1;
currentWhitespaceHeight = 0;
} else {
currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
}
let currentVerticalOffset = startLineNumberVerticalOffset;
let currentLineRelativeOffset = currentVerticalOffset;
// IE (all versions) cannot handle units above about 1,533,908 px, so every 500k pixels bring numbers down
const STEP_SIZE = 500000;
let bigNumbersDelta = 0;
if (startLineNumberVerticalOffset >= STEP_SIZE) {
// Compute a delta that guarantees that lines are positioned at `lineHeight` increments
bigNumbersDelta = Math.floor(startLineNumberVerticalOffset / STEP_SIZE) * STEP_SIZE;
bigNumbersDelta = Math.floor(bigNumbersDelta / lineHeight) * lineHeight;
currentLineRelativeOffset -= bigNumbersDelta;
}
let linesOffsets: number[] = [];
const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2;
let centeredLineNumber = -1;
// Figure out how far the lines go
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
if (centeredLineNumber === -1) {
let currentLineTop = currentVerticalOffset;
let currentLineBottom = currentVerticalOffset + lineHeight;
if ((currentLineTop <= verticalCenter && verticalCenter < currentLineBottom) || currentLineTop > verticalCenter) {
centeredLineNumber = lineNumber;
}
}
// Count current line height in the vertical offsets
currentVerticalOffset += lineHeight;
linesOffsets[lineNumber - startLineNumber] = currentLineRelativeOffset;
// Next line starts immediately after this one
currentLineRelativeOffset += lineHeight;
while (currentWhitespaceAfterLineNumber === lineNumber) {
// Push down next line with the height of the current whitespace
currentLineRelativeOffset += currentWhitespaceHeight;
// Count current whitespace in the vertical offsets
currentVerticalOffset += currentWhitespaceHeight;
whitespaceIndex++;
if (whitespaceIndex >= whitespaceCount) {
currentWhitespaceAfterLineNumber = endLineNumber + 1;
} else {
currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
}
}
if (currentVerticalOffset >= verticalOffset2) {
// We have covered the entire viewport area, time to stop
endLineNumber = lineNumber;
break;
}
}
if (centeredLineNumber === -1) {
centeredLineNumber = endLineNumber;
}
const endLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(endLineNumber) | 0;
let completelyVisibleStartLineNumber = startLineNumber;
let completelyVisibleEndLineNumber = endLineNumber;
if (completelyVisibleStartLineNumber < completelyVisibleEndLineNumber) {
if (startLineNumberVerticalOffset < verticalOffset1) {
completelyVisibleStartLineNumber++;
}
}
if (completelyVisibleStartLineNumber < completelyVisibleEndLineNumber) {
if (endLineNumberVerticalOffset + lineHeight > verticalOffset2) {
completelyVisibleEndLineNumber--;
}
}
return {
bigNumbersDelta: bigNumbersDelta,
startLineNumber: startLineNumber,
endLineNumber: endLineNumber,
relativeVerticalOffset: linesOffsets,
centeredLineNumber: centeredLineNumber,
completelyVisibleStartLineNumber: completelyVisibleStartLineNumber,
completelyVisibleEndLineNumber: completelyVisibleEndLineNumber
};
}
public getVerticalOffsetForWhitespaceIndex(whitespaceIndex: number): number {
whitespaceIndex = whitespaceIndex | 0;
let afterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex);
let previousLinesHeight: number;
if (afterLineNumber >= 1) {
previousLinesHeight = this._lineHeight * afterLineNumber;
} else {
previousLinesHeight = 0;
}
let previousWhitespacesHeight: number;
if (whitespaceIndex > 0) {
previousWhitespacesHeight = this._whitespaces.getAccumulatedHeight(whitespaceIndex - 1);
} else {
previousWhitespacesHeight = 0;
}
return previousLinesHeight + previousWhitespacesHeight;
}
public getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset: number): number {
verticalOffset = verticalOffset | 0;
let midWhitespaceIndex: number,
minWhitespaceIndex = 0,
maxWhitespaceIndex = this._whitespaces.getCount() - 1,
midWhitespaceVerticalOffset: number,
midWhitespaceHeight: number;
if (maxWhitespaceIndex < 0) {
return -1;
}
// Special case: nothing to be found
let maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex);
let maxWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(maxWhitespaceIndex);
if (verticalOffset >= maxWhitespaceVerticalOffset + maxWhitespaceHeight) {
return -1;
}
while (minWhitespaceIndex < maxWhitespaceIndex) {
midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2);
midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex);
midWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(midWhitespaceIndex);
if (verticalOffset >= midWhitespaceVerticalOffset + midWhitespaceHeight) {
// vertical offset is after whitespace
minWhitespaceIndex = midWhitespaceIndex + 1;
} else if (verticalOffset >= midWhitespaceVerticalOffset) {
// Hit
return midWhitespaceIndex;
} else {
// vertical offset is before whitespace, but midWhitespaceIndex might still be what we're searching for
maxWhitespaceIndex = midWhitespaceIndex;
}
}
return minWhitespaceIndex;
}
/**
* Get exactly the whitespace that is layouted at `verticalOffset`.
*
* @param verticalOffset The vertical offset.
* @return Precisely the whitespace that is layouted at `verticaloffset` or null.
*/
public getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData {
verticalOffset = verticalOffset | 0;
let candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset);
if (candidateIndex < 0) {
return null;
}
if (candidateIndex >= this._whitespaces.getCount()) {
return null;
}
let candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex);
if (candidateTop > verticalOffset) {
return null;
}
let candidateHeight = this._whitespaces.getHeightForWhitespaceIndex(candidateIndex);
let candidateId = this._whitespaces.getIdForWhitespaceIndex(candidateIndex);
let candidateAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(candidateIndex);
return {
id: candidateId,
afterLineNumber: candidateAfterLineNumber,
verticalOffset: candidateTop,
height: candidateHeight
};
}
/**
* Get a list of whitespaces that are positioned between `verticalOffset1` and `verticalOffset2`.
*
* @param verticalOffset1 The beginning of the viewport.
* @param verticalOffset2 The end of the viewport.
* @return An array with all the whitespaces in the viewport. If no whitespace is in viewport, the array is empty.
*/
public getWhitespaceViewportData(verticalOffset1: number, verticalOffset2: number): IViewWhitespaceViewportData[] {
verticalOffset1 = verticalOffset1 | 0;
verticalOffset2 = verticalOffset2 | 0;
let startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1);
let endIndex = this._whitespaces.getCount() - 1;
if (startIndex < 0) {
return [];
}
let result: IViewWhitespaceViewportData[] = [];
for (let i = startIndex; i <= endIndex; i++) {
let top = this.getVerticalOffsetForWhitespaceIndex(i);
let height = this._whitespaces.getHeightForWhitespaceIndex(i);
if (top >= verticalOffset2) {
break;
}
result.push({
id: this._whitespaces.getIdForWhitespaceIndex(i),
afterLineNumber: this._whitespaces.getAfterLineNumberForWhitespaceIndex(i),
verticalOffset: top,
height: height
});
}
return result;
}
/**
* Get all whitespaces.
*/
public getWhitespaces(): IEditorWhitespace[] {
return this._whitespaces.getWhitespaces(this._lineHeight);
}
}

View File

@@ -0,0 +1,280 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Scrollable, ScrollEvent, ScrollbarVisibility, IScrollDimensions, IScrollPosition } from 'vs/base/common/scrollable';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout';
import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
import Event from 'vs/base/common/event';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
const SMOOTH_SCROLLING_TIME = 125;
export class ViewLayout extends Disposable implements IViewLayout {
static LINES_HORIZONTAL_EXTRA_PX = 30;
private readonly _configuration: editorCommon.IConfiguration;
private readonly _linesLayout: LinesLayout;
public readonly scrollable: Scrollable;
public readonly onDidScroll: Event<ScrollEvent>;
constructor(configuration: editorCommon.IConfiguration, lineCount: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
super();
this._configuration = configuration;
this._linesLayout = new LinesLayout(lineCount, this._configuration.editor.lineHeight);
this.scrollable = this._register(new Scrollable(0, scheduleAtNextAnimationFrame));
this._configureSmoothScrollDuration();
this.scrollable.setScrollDimensions({
width: configuration.editor.layoutInfo.contentWidth,
height: configuration.editor.layoutInfo.contentHeight
});
this.onDidScroll = this.scrollable.onScroll;
this._updateHeight();
}
public dispose(): void {
super.dispose();
}
public getScrollable(): Scrollable {
return this.scrollable;
}
public onHeightMaybeChanged(): void {
this._updateHeight();
}
private _configureSmoothScrollDuration(): void {
this.scrollable.setSmoothScrollDuration(this._configuration.editor.viewInfo.smoothScrolling ? SMOOTH_SCROLLING_TIME : 0);
}
// ---- begin view event handlers
public onConfigurationChanged(e: IConfigurationChangedEvent): void {
if (e.lineHeight) {
this._linesLayout.setLineHeight(this._configuration.editor.lineHeight);
}
if (e.layoutInfo) {
this.scrollable.setScrollDimensions({
width: this._configuration.editor.layoutInfo.contentWidth,
height: this._configuration.editor.layoutInfo.contentHeight
});
}
if (e.viewInfo) {
this._configureSmoothScrollDuration();
}
this._updateHeight();
}
public onFlushed(lineCount: number): void {
this._linesLayout.onFlushed(lineCount);
this._updateHeight();
}
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber);
this._updateHeight();
}
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber);
this._updateHeight();
}
// ---- end view event handlers
private _getHorizontalScrollbarHeight(scrollDimensions: IScrollDimensions): number {
if (this._configuration.editor.viewInfo.scrollbar.horizontal === ScrollbarVisibility.Hidden) {
// horizontal scrollbar not visible
return 0;
}
if (scrollDimensions.width >= scrollDimensions.scrollWidth) {
// horizontal scrollbar not visible
return 0;
}
return this._configuration.editor.viewInfo.scrollbar.horizontalScrollbarSize;
}
private _getTotalHeight(): number {
const scrollDimensions = this.scrollable.getScrollDimensions();
let result = this._linesLayout.getLinesTotalHeight();
if (this._configuration.editor.viewInfo.scrollBeyondLastLine) {
result += scrollDimensions.height - this._configuration.editor.lineHeight;
} else {
result += this._getHorizontalScrollbarHeight(scrollDimensions);
}
return Math.max(scrollDimensions.height, result);
}
private _updateHeight(): void {
this.scrollable.setScrollDimensions({
scrollHeight: this._getTotalHeight()
});
}
// ---- Layouting logic
public getCurrentViewport(): Viewport {
const scrollDimensions = this.scrollable.getScrollDimensions();
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
return new Viewport(
currentScrollPosition.scrollTop,
currentScrollPosition.scrollLeft,
scrollDimensions.width,
scrollDimensions.height
);
}
public getFutureViewport(): Viewport {
const scrollDimensions = this.scrollable.getScrollDimensions();
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
return new Viewport(
currentScrollPosition.scrollTop,
currentScrollPosition.scrollLeft,
scrollDimensions.width,
scrollDimensions.height
);
}
private _computeScrollWidth(maxLineWidth: number, viewportWidth: number): number {
let isViewportWrapping = this._configuration.editor.wrappingInfo.isViewportWrapping;
if (!isViewportWrapping) {
return Math.max(maxLineWidth + ViewLayout.LINES_HORIZONTAL_EXTRA_PX, viewportWidth);
}
return Math.max(maxLineWidth, viewportWidth);
}
public onMaxLineWidthChanged(maxLineWidth: number): void {
let newScrollWidth = this._computeScrollWidth(maxLineWidth, this.getCurrentViewport().width);
this.scrollable.setScrollDimensions({
scrollWidth: newScrollWidth
});
// The height might depend on the fact that there is a horizontal scrollbar or not
this._updateHeight();
}
// ---- view state
public saveState(): editorCommon.IViewState {
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
let scrollTop = currentScrollPosition.scrollTop;
let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
let whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport);
return {
scrollTop: scrollTop,
scrollTopWithoutViewZones: scrollTop - whitespaceAboveFirstLine,
scrollLeft: currentScrollPosition.scrollLeft
};
}
public restoreState(state: editorCommon.IViewState): void {
let restoreScrollTop = state.scrollTop;
if (typeof state.scrollTopWithoutViewZones === 'number' && !this._linesLayout.hasWhitespace()) {
restoreScrollTop = state.scrollTopWithoutViewZones;
}
this.scrollable.setScrollPositionNow({
scrollLeft: state.scrollLeft,
scrollTop: restoreScrollTop
});
}
// ---- IVerticalLayoutProvider
public addWhitespace(afterLineNumber: number, ordinal: number, height: number): number {
return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height);
}
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight);
}
public removeWhitespace(id: number): boolean {
return this._linesLayout.removeWhitespace(id);
}
public getVerticalOffsetForLineNumber(lineNumber: number): number {
return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber);
}
public isAfterLines(verticalOffset: number): boolean {
return this._linesLayout.isAfterLines(verticalOffset);
}
public getLineNumberAtVerticalOffset(verticalOffset: number): number {
return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset);
}
public getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData {
return this._linesLayout.getWhitespaceAtVerticalOffset(verticalOffset);
}
public getLinesViewportData(): IPartialViewLinesViewportData {
const visibleBox = this.getCurrentViewport();
return this._linesLayout.getLinesViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
}
public getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData {
// do some minimal validations on scrollTop
const scrollDimensions = this.scrollable.getScrollDimensions();
if (scrollTop + scrollDimensions.height > scrollDimensions.scrollHeight) {
scrollTop = scrollDimensions.scrollHeight - scrollDimensions.height;
}
if (scrollTop < 0) {
scrollTop = 0;
}
return this._linesLayout.getLinesViewportData(scrollTop, scrollTop + scrollDimensions.height);
}
public getWhitespaceViewportData(): IViewWhitespaceViewportData[] {
const visibleBox = this.getCurrentViewport();
return this._linesLayout.getWhitespaceViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
}
public getWhitespaces(): IEditorWhitespace[] {
return this._linesLayout.getWhitespaces();
}
// ---- IScrollingProvider
public getScrollWidth(): number {
const scrollDimensions = this.scrollable.getScrollDimensions();
return scrollDimensions.scrollWidth;
}
public getScrollHeight(): number {
const scrollDimensions = this.scrollable.getScrollDimensions();
return scrollDimensions.scrollHeight;
}
public getCurrentScrollLeft(): number {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollLeft;
}
public getCurrentScrollTop(): number {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollTop;
}
public validateScrollPosition(scrollPosition: editorCommon.INewScrollPosition): IScrollPosition {
return this.scrollable.validateScrollPosition(scrollPosition);
}
public setScrollPositionNow(position: editorCommon.INewScrollPosition): void {
this.scrollable.setScrollPositionNow(position);
}
public setScrollPositionSmooth(position: editorCommon.INewScrollPosition): void {
this.scrollable.setScrollPositionSmooth(position);
}
public deltaScrollNow(deltaScrollLeft: number, deltaScrollTop: number): void {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
this.scrollable.setScrollPositionNow({
scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft,
scrollTop: currentScrollPosition.scrollTop + deltaScrollTop
});
}
}

View File

@@ -0,0 +1,759 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { CharCode } from 'vs/base/common/charCode';
import { LineDecoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/lineDecorations';
import * as strings from 'vs/base/common/strings';
import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/stringBuilder';
export const enum RenderWhitespace {
None = 0,
Boundary = 1,
All = 2
}
class LinePart {
_linePartBrand: void;
/**
* last char index of this token (not inclusive).
*/
public readonly endIndex: number;
public readonly type: string;
constructor(endIndex: number, type: string) {
this.endIndex = endIndex;
this.type = type;
}
}
export class RenderLineInput {
public readonly useMonospaceOptimizations: boolean;
public readonly lineContent: string;
public readonly mightContainRTL: boolean;
public readonly fauxIndentLength: number;
public readonly lineTokens: ViewLineToken[];
public readonly lineDecorations: LineDecoration[];
public readonly tabSize: number;
public readonly spaceWidth: number;
public readonly stopRenderingLineAfter: number;
public readonly renderWhitespace: RenderWhitespace;
public readonly renderControlCharacters: boolean;
public readonly fontLigatures: boolean;
constructor(
useMonospaceOptimizations: boolean,
lineContent: string,
mightContainRTL: boolean,
fauxIndentLength: number,
lineTokens: ViewLineToken[],
lineDecorations: LineDecoration[],
tabSize: number,
spaceWidth: number,
stopRenderingLineAfter: number,
renderWhitespace: 'none' | 'boundary' | 'all',
renderControlCharacters: boolean,
fontLigatures: boolean
) {
this.useMonospaceOptimizations = useMonospaceOptimizations;
this.lineContent = lineContent;
this.mightContainRTL = mightContainRTL;
this.fauxIndentLength = fauxIndentLength;
this.lineTokens = lineTokens;
this.lineDecorations = lineDecorations;
this.tabSize = tabSize;
this.spaceWidth = spaceWidth;
this.stopRenderingLineAfter = stopRenderingLineAfter;
this.renderWhitespace = (
renderWhitespace === 'all'
? RenderWhitespace.All
: renderWhitespace === 'boundary'
? RenderWhitespace.Boundary
: RenderWhitespace.None
);
this.renderControlCharacters = renderControlCharacters;
this.fontLigatures = fontLigatures;
}
public equals(other: RenderLineInput): boolean {
return (
this.useMonospaceOptimizations === other.useMonospaceOptimizations
&& this.lineContent === other.lineContent
&& this.mightContainRTL === other.mightContainRTL
&& this.fauxIndentLength === other.fauxIndentLength
&& this.tabSize === other.tabSize
&& this.spaceWidth === other.spaceWidth
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
&& this.renderWhitespace === other.renderWhitespace
&& this.renderControlCharacters === other.renderControlCharacters
&& this.fontLigatures === other.fontLigatures
&& LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations)
&& ViewLineToken.equalsArr(this.lineTokens, other.lineTokens)
);
}
}
export const enum CharacterMappingConstants {
PART_INDEX_MASK = 0b11111111111111110000000000000000,
CHAR_INDEX_MASK = 0b00000000000000001111111111111111,
CHAR_INDEX_OFFSET = 0,
PART_INDEX_OFFSET = 16
}
/**
* Provides a both direction mapping between a line's character and its rendered position.
*/
export class CharacterMapping {
public static getPartIndex(partData: number): number {
return (partData & CharacterMappingConstants.PART_INDEX_MASK) >>> CharacterMappingConstants.PART_INDEX_OFFSET;
}
public static getCharIndex(partData: number): number {
return (partData & CharacterMappingConstants.CHAR_INDEX_MASK) >>> CharacterMappingConstants.CHAR_INDEX_OFFSET;
}
public readonly length: number;
private readonly _data: Uint32Array;
private readonly _absoluteOffsets: Uint32Array;
constructor(length: number, partCount: number) {
this.length = length;
this._data = new Uint32Array(this.length);
this._absoluteOffsets = new Uint32Array(this.length);
}
public setPartData(charOffset: number, partIndex: number, charIndex: number, partAbsoluteOffset: number): void {
let partData = (
(partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
| (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET)
) >>> 0;
this._data[charOffset] = partData;
this._absoluteOffsets[charOffset] = partAbsoluteOffset + charIndex;
}
public getAbsoluteOffsets(): Uint32Array {
return this._absoluteOffsets;
}
public charOffsetToPartData(charOffset: number): number {
if (this.length === 0) {
return 0;
}
if (charOffset < 0) {
return this._data[0];
}
if (charOffset >= this.length) {
return this._data[this.length - 1];
}
return this._data[charOffset];
}
public partDataToCharOffset(partIndex: number, partLength: number, charIndex: number): number {
if (this.length === 0) {
return 0;
}
let searchEntry = (
(partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
| (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET)
) >>> 0;
let min = 0;
let max = this.length - 1;
while (min + 1 < max) {
let mid = ((min + max) >>> 1);
let midEntry = this._data[mid];
if (midEntry === searchEntry) {
return mid;
} else if (midEntry > searchEntry) {
max = mid;
} else {
min = mid;
}
}
if (min === max) {
return min;
}
let minEntry = this._data[min];
let maxEntry = this._data[max];
if (minEntry === searchEntry) {
return min;
}
if (maxEntry === searchEntry) {
return max;
}
let minPartIndex = CharacterMapping.getPartIndex(minEntry);
let minCharIndex = CharacterMapping.getCharIndex(minEntry);
let maxPartIndex = CharacterMapping.getPartIndex(maxEntry);
let maxCharIndex: number;
if (minPartIndex !== maxPartIndex) {
// sitting between parts
maxCharIndex = partLength;
} else {
maxCharIndex = CharacterMapping.getCharIndex(maxEntry);
}
let minEntryDistance = charIndex - minCharIndex;
let maxEntryDistance = maxCharIndex - charIndex;
if (minEntryDistance <= maxEntryDistance) {
return min;
}
return max;
}
}
export class RenderLineOutput {
_renderLineOutputBrand: void;
readonly characterMapping: CharacterMapping;
readonly containsRTL: boolean;
readonly containsForeignElements: boolean;
constructor(characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) {
this.characterMapping = characterMapping;
this.containsRTL = containsRTL;
this.containsForeignElements = containsForeignElements;
}
}
export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): RenderLineOutput {
if (input.lineContent.length === 0) {
let containsForeignElements = false;
// This is basically for IE's hit test to work
let content: string = '<span><span>\u00a0</span></span>';
if (input.lineDecorations.length > 0) {
// This line is empty, but it contains inline decorations
let classNames: string[] = [];
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
const lineDecoration = input.lineDecorations[i];
if (lineDecoration.insertsBeforeOrAfter) {
classNames[i] = input.lineDecorations[i].className;
containsForeignElements = true;
}
}
if (containsForeignElements) {
content = `<span><span class="${classNames.join(' ')}">\u00a0</span></span>`;
}
}
sb.appendASCIIString(content);
return new RenderLineOutput(
new CharacterMapping(0, 0),
false,
containsForeignElements
);
}
return _renderLine(resolveRenderLineInput(input), sb);
}
export class RenderLineOutput2 {
constructor(
public readonly characterMapping: CharacterMapping,
public readonly html: string,
public readonly containsRTL: boolean,
public readonly containsForeignElements: boolean
) {
}
}
export function renderViewLine2(input: RenderLineInput): RenderLineOutput2 {
let sb = createStringBuilder(10000);
let out = renderViewLine(input, sb);
return new RenderLineOutput2(out.characterMapping, sb.build(), out.containsRTL, out.containsForeignElements);
}
class ResolvedRenderLineInput {
constructor(
public readonly fontIsMonospace: boolean,
public readonly lineContent: string,
public readonly len: number,
public readonly isOverflowing: boolean,
public readonly parts: LinePart[],
public readonly containsForeignElements: boolean,
public readonly tabSize: number,
public readonly containsRTL: boolean,
public readonly spaceWidth: number,
public readonly renderWhitespace: RenderWhitespace,
public readonly renderControlCharacters: boolean,
) {
//
}
}
function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput {
const useMonospaceOptimizations = input.useMonospaceOptimizations;
const lineContent = input.lineContent;
let isOverflowing: boolean;
let len: number;
if (input.stopRenderingLineAfter !== -1 && input.stopRenderingLineAfter < lineContent.length) {
isOverflowing = true;
len = input.stopRenderingLineAfter;
} else {
isOverflowing = false;
len = lineContent.length;
}
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary) {
tokens = _applyRenderWhitespace(lineContent, len, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.renderWhitespace === RenderWhitespace.Boundary);
}
let containsForeignElements = false;
if (input.lineDecorations.length > 0) {
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
const lineDecoration = input.lineDecorations[i];
if (lineDecoration.insertsBeforeOrAfter) {
containsForeignElements = true;
break;
}
}
tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
}
let containsRTL = false;
if (input.mightContainRTL) {
containsRTL = strings.containsRTL(lineContent);
}
if (!containsRTL && !input.fontLigatures) {
tokens = splitLargeTokens(lineContent, tokens);
}
return new ResolvedRenderLineInput(
useMonospaceOptimizations,
lineContent,
len,
isOverflowing,
tokens,
containsForeignElements,
input.tabSize,
containsRTL,
input.spaceWidth,
input.renderWhitespace,
input.renderControlCharacters
);
}
/**
* In the rendering phase, characters are always looped until token.endIndex.
* Ensure that all tokens end before `len` and the last one ends precisely at `len`.
*/
function transformAndRemoveOverflowing(tokens: ViewLineToken[], fauxIndentLength: number, len: number): LinePart[] {
let result: LinePart[] = [], resultLen = 0;
// The faux indent part of the line should have no token type
if (fauxIndentLength > 0) {
result[resultLen++] = new LinePart(fauxIndentLength, '');
}
for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) {
const token = tokens[tokenIndex];
const endIndex = token.endIndex;
if (endIndex <= fauxIndentLength) {
// The faux indent part of the line should have no token type
continue;
}
const type = token.getType();
if (endIndex >= len) {
result[resultLen++] = new LinePart(len, type);
break;
}
result[resultLen++] = new LinePart(endIndex, type);
}
return result;
}
/**
* written as a const enum to get value inlining.
*/
const enum Constants {
LongToken = 50
}
/**
* See https://github.com/Microsoft/vscode/issues/6885.
* It appears that having very large spans causes very slow reading of character positions.
* So here we try to avoid that.
*/
function splitLargeTokens(lineContent: string, tokens: LinePart[]): LinePart[] {
let lastTokenEndIndex = 0;
let result: LinePart[] = [], resultLen = 0;
for (let i = 0, len = tokens.length; i < len; i++) {
const token = tokens[i];
const tokenEndIndex = token.endIndex;
let diff = (tokenEndIndex - lastTokenEndIndex);
if (diff > Constants.LongToken) {
const tokenType = token.type;
const piecesCount = Math.ceil(diff / Constants.LongToken);
for (let j = 1; j < piecesCount; j++) {
let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken);
let lastCharInPiece = lineContent.charCodeAt(pieceEndIndex - 1);
if (strings.isHighSurrogate(lastCharInPiece)) {
// Don't cut in the middle of a surrogate pair
pieceEndIndex--;
}
result[resultLen++] = new LinePart(pieceEndIndex, tokenType);
}
result[resultLen++] = new LinePart(tokenEndIndex, tokenType);
} else {
result[resultLen++] = token;
}
lastTokenEndIndex = tokenEndIndex;
}
return result;
}
/**
* Whitespace is rendered by "replacing" tokens with a special-purpose `vs-whitespace` type that is later recognized in the rendering phase.
* Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
* The rendering phase will generate `style="width:..."` for these tokens.
*/
function _applyRenderWhitespace(lineContent: string, len: number, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, onlyBoundary: boolean): LinePart[] {
let result: LinePart[] = [], resultLen = 0;
let tokenIndex = 0;
let tokenType = tokens[tokenIndex].type;
let tokenEndIndex = tokens[tokenIndex].endIndex;
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
let lastNonWhitespaceIndex: number;
if (firstNonWhitespaceIndex === -1) {
// The entire line is whitespace
firstNonWhitespaceIndex = len;
lastNonWhitespaceIndex = len;
} else {
lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
}
let tmpIndent = 0;
for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex);
if (chCode === CharCode.Tab) {
tmpIndent = tabSize;
} else {
tmpIndent++;
}
}
tmpIndent = tmpIndent % tabSize;
let wasInWhitespace = false;
for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex);
let isInWhitespace: boolean;
if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) {
// in leading or trailing whitespace
isInWhitespace = true;
} else if (chCode === CharCode.Tab) {
// a tab character is rendered both in all and boundary cases
isInWhitespace = true;
} else if (chCode === CharCode.Space) {
// hit a space character
if (onlyBoundary) {
// rendering only boundary whitespace
if (wasInWhitespace) {
isInWhitespace = true;
} else {
const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : CharCode.Null);
isInWhitespace = (nextChCode === CharCode.Space || nextChCode === CharCode.Tab);
}
} else {
isInWhitespace = true;
}
} else {
isInWhitespace = false;
}
if (wasInWhitespace) {
// was in whitespace token
if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
// leaving whitespace token or entering a new indent
result[resultLen++] = new LinePart(charIndex, 'vs-whitespace');
tmpIndent = tmpIndent % tabSize;
}
} else {
// was in regular token
if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
result[resultLen++] = new LinePart(charIndex, tokenType);
tmpIndent = tmpIndent % tabSize;
}
}
if (chCode === CharCode.Tab) {
tmpIndent = tabSize;
} else {
tmpIndent++;
}
wasInWhitespace = isInWhitespace;
if (charIndex === tokenEndIndex) {
tokenIndex++;
tokenType = tokens[tokenIndex].type;
tokenEndIndex = tokens[tokenIndex].endIndex;
}
}
if (wasInWhitespace) {
// was in whitespace token
result[resultLen++] = new LinePart(len, 'vs-whitespace');
} else {
// was in regular token
result[resultLen++] = new LinePart(len, tokenType);
}
return result;
}
/**
* Inline decorations are "merged" on top of tokens.
* Special care must be taken when multiple inline decorations are at play and they overlap.
*/
function _applyInlineDecorations(lineContent: string, len: number, tokens: LinePart[], _lineDecorations: LineDecoration[]): LinePart[] {
_lineDecorations.sort(LineDecoration.compare);
const lineDecorations = LineDecorationsNormalizer.normalize(_lineDecorations);
const lineDecorationsLen = lineDecorations.length;
let lineDecorationIndex = 0;
let result: LinePart[] = [], resultLen = 0, lastResultEndIndex = 0;
for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) {
const token = tokens[tokenIndex];
const tokenEndIndex = token.endIndex;
const tokenType = token.type;
while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) {
const lineDecoration = lineDecorations[lineDecorationIndex];
if (lineDecoration.startOffset > lastResultEndIndex) {
lastResultEndIndex = lineDecoration.startOffset;
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
}
if (lineDecoration.endOffset + 1 <= tokenEndIndex) {
// This line decoration ends before this token ends
lastResultEndIndex = lineDecoration.endOffset + 1;
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
lineDecorationIndex++;
} else {
// This line decoration continues on to the next token
lastResultEndIndex = tokenEndIndex;
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className);
break;
}
}
if (tokenEndIndex > lastResultEndIndex) {
lastResultEndIndex = tokenEndIndex;
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType);
}
}
return result;
}
/**
* This function is on purpose not split up into multiple functions to allow runtime type inference (i.e. performance reasons).
* Notice how all the needed data is fully resolved and passed in (i.e. no other calls).
*/
function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): RenderLineOutput {
const fontIsMonospace = input.fontIsMonospace;
const containsForeignElements = input.containsForeignElements;
const lineContent = input.lineContent;
const len = input.len;
const isOverflowing = input.isOverflowing;
const parts = input.parts;
const tabSize = input.tabSize;
const containsRTL = input.containsRTL;
const spaceWidth = input.spaceWidth;
const renderWhitespace = input.renderWhitespace;
const renderControlCharacters = input.renderControlCharacters;
const characterMapping = new CharacterMapping(len + 1, parts.length);
let charIndex = 0;
let tabsCharDelta = 0;
let charOffsetInPart = 0;
let prevPartContentCnt = 0;
let partAbsoluteOffset = 0;
sb.appendASCIIString('<span>');
for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
partAbsoluteOffset += prevPartContentCnt;
const part = parts[partIndex];
const partEndIndex = part.endIndex;
const partType = part.type;
const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('vs-whitespace') >= 0));
charOffsetInPart = 0;
sb.appendASCIIString('<span class="');
sb.appendASCIIString(partType);
sb.appendASCII(CharCode.DoubleQuote);
if (partRendersWhitespace) {
let partContentCnt = 0;
{
let _charIndex = charIndex;
let _tabsCharDelta = tabsCharDelta;
let _charOffsetInPart = charOffsetInPart;
for (; _charIndex < partEndIndex; _charIndex++) {
const charCode = lineContent.charCodeAt(_charIndex);
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize;
_tabsCharDelta += insertSpacesCount - 1;
_charOffsetInPart += insertSpacesCount - 1;
partContentCnt += insertSpacesCount;
} else {
partContentCnt++;
}
_charOffsetInPart++;
}
}
if (!fontIsMonospace && !containsForeignElements) {
sb.appendASCIIString(' style="width:');
sb.appendASCIIString(String(spaceWidth * partContentCnt));
sb.appendASCIIString('px"');
}
sb.appendASCII(CharCode.GreaterThan);
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex);
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
if (insertSpacesCount > 0) {
sb.write1(0x2192); // &rarr;
insertSpacesCount--;
}
while (insertSpacesCount > 0) {
sb.write1(0xA0); // &nbsp;
insertSpacesCount--;
}
} else {
// must be CharCode.Space
sb.write1(0xb7); // &middot;
}
charOffsetInPart++;
}
prevPartContentCnt = partContentCnt;
} else {
let partContentCnt = 0;
if (containsRTL) {
sb.appendASCIIString(' dir="ltr"');
}
sb.appendASCII(CharCode.GreaterThan);
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex);
switch (charCode) {
case CharCode.Tab:
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
while (insertSpacesCount > 0) {
sb.write1(0xA0); // &nbsp;
partContentCnt++;
insertSpacesCount--;
}
break;
case CharCode.Space:
sb.write1(0xA0); // &nbsp;
partContentCnt++;
break;
case CharCode.LessThan:
sb.appendASCIIString('&lt;');
partContentCnt++;
break;
case CharCode.GreaterThan:
sb.appendASCIIString('&gt;');
partContentCnt++;
break;
case CharCode.Ampersand:
sb.appendASCIIString('&amp;');
partContentCnt++;
break;
case CharCode.Null:
sb.appendASCIIString('&#00;');
partContentCnt++;
break;
case CharCode.UTF8_BOM:
case CharCode.LINE_SEPARATOR_2028:
sb.write1(0xfffd);
partContentCnt++;
break;
default:
if (renderControlCharacters && charCode < 32) {
sb.write1(9216 + charCode);
partContentCnt++;
} else {
sb.write1(charCode);
partContentCnt++;
}
}
charOffsetInPart++;
}
prevPartContentCnt = partContentCnt;
}
sb.appendASCIIString('</span>');
}
// When getting client rects for the last character, we will position the
// text range at the end of the span, insteaf of at the beginning of next span
characterMapping.setPartData(len, parts.length - 1, charOffsetInPart, partAbsoluteOffset);
if (isOverflowing) {
sb.appendASCIIString('<span>&hellip;</span>');
}
sb.appendASCIIString('</span>');
return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
}

View File

@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ViewLineRenderingData, IViewModel, ViewModelDecoration, IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
export interface IPartialViewLinesViewportData {
/**
* Value to be substracted from `scrollTop` (in order to vertical offset numbers < 1MM)
*/
readonly bigNumbersDelta: number;
/**
* The first (partially) visible line number.
*/
readonly startLineNumber: number;
/**
* The last (partially) visible line number.
*/
readonly endLineNumber: number;
/**
* relativeVerticalOffset[i] is the `top` position for line at `i` + `startLineNumber`.
*/
readonly relativeVerticalOffset: number[];
/**
* The centered line in the viewport.
*/
readonly centeredLineNumber: number;
/**
* The first completely visible line number.
*/
readonly completelyVisibleStartLineNumber: number;
/**
* The last completely visible line number.
*/
readonly completelyVisibleEndLineNumber: number;
}
/**
* Contains all data needed to render at a specific viewport.
*/
export class ViewportData {
public readonly selections: Selection[];
/**
* The line number at which to start rendering (inclusive).
*/
public readonly startLineNumber: number;
/**
* The line number at which to end rendering (inclusive).
*/
public readonly endLineNumber: number;
/**
* relativeVerticalOffset[i] is the `top` position for line at `i` + `startLineNumber`.
*/
public readonly relativeVerticalOffset: number[];
/**
* The viewport as a range (startLineNumber,1) -> (endLineNumber,maxColumn(endLineNumber)).
*/
public readonly visibleRange: Range;
/**
* Value to be substracted from `scrollTop` (in order to vertical offset numbers < 1MM)
*/
public readonly bigNumbersDelta: number;
/**
* Positioning information about gaps whitespace.
*/
public readonly whitespaceViewportData: IViewWhitespaceViewportData[];
private readonly _model: IViewModel;
constructor(
selections: Selection[],
partialData: IPartialViewLinesViewportData,
whitespaceViewportData: IViewWhitespaceViewportData[],
model: IViewModel
) {
this.selections = selections;
this.startLineNumber = partialData.startLineNumber | 0;
this.endLineNumber = partialData.endLineNumber | 0;
this.relativeVerticalOffset = partialData.relativeVerticalOffset;
this.bigNumbersDelta = partialData.bigNumbersDelta | 0;
this.whitespaceViewportData = whitespaceViewportData;
this._model = model;
this.visibleRange = new Range(
partialData.startLineNumber,
this._model.getLineMinColumn(partialData.startLineNumber),
partialData.endLineNumber,
this._model.getLineMaxColumn(partialData.endLineNumber)
);
}
public getViewLineRenderingData(lineNumber: number): ViewLineRenderingData {
return this._model.getViewLineRenderingData(this.visibleRange, lineNumber);
}
public getDecorationsInViewport(): ViewModelDecoration[] {
return this._model.getDecorationsInViewport(this.visibleRange);
}
}

View File

@@ -0,0 +1,465 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface IEditorWhitespace {
readonly id: number;
readonly afterLineNumber: number;
readonly heightInLines: number;
}
/**
* Represent whitespaces in between lines and provide fast CRUD management methods.
* The whitespaces are sorted ascending by `afterLineNumber`.
*/
export class WhitespaceComputer {
/**
* heights[i] is the height in pixels for whitespace at index i
*/
private _heights: number[];
/**
* afterLineNumbers[i] is the line number whitespace at index i is after
*/
private _afterLineNumbers: number[];
/**
* ordinals[i] is the orinal of the whitespace at index i
*/
private _ordinals: number[];
/**
* prefixSum[i] = SUM(heights[j]), 1 <= j <= i
*/
private _prefixSum: number[];
/**
* prefixSum[i], 1 <= i <= prefixSumValidIndex can be trusted
*/
private _prefixSumValidIndex: number;
/**
* ids[i] is the whitespace id of whitespace at index i
*/
private _ids: number[];
/**
* index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members)
*/
private _whitespaceId2Index: {
[id: string]: number;
};
/**
* last whitespace id issued
*/
private _lastWhitespaceId: number;
constructor() {
this._heights = [];
this._ids = [];
this._afterLineNumbers = [];
this._ordinals = [];
this._prefixSum = [];
this._prefixSumValidIndex = -1;
this._whitespaceId2Index = {};
this._lastWhitespaceId = 0;
}
/**
* Find the insertion index for a new value inside a sorted array of values.
* If the value is already present in the sorted array, the insertion index will be after the already existing value.
*/
public static findInsertionIndex(sortedArray: number[], value: number, ordinals: number[], valueOrdinal: number): number {
let low = 0;
let high = sortedArray.length;
while (low < high) {
let mid = ((low + high) >>> 1);
if (value === sortedArray[mid]) {
if (valueOrdinal < ordinals[mid]) {
high = mid;
} else {
low = mid + 1;
}
} else if (value < sortedArray[mid]) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
/**
* Insert a new whitespace of a certain height after a line number.
* The whitespace has a "sticky" characteristic.
* Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line.
*
* @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below.
* @param heightInPx The height of the whitespace, in pixels.
* @return An id that can be used later to mutate or delete the whitespace
*/
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number): number {
afterLineNumber = afterLineNumber | 0;
ordinal = ordinal | 0;
heightInPx = heightInPx | 0;
let id = (++this._lastWhitespaceId);
let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, afterLineNumber, this._ordinals, ordinal);
this._insertWhitespaceAtIndex(id, insertionIndex, afterLineNumber, ordinal, heightInPx);
return id;
}
private _insertWhitespaceAtIndex(id: number, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number): void {
id = id | 0;
insertIndex = insertIndex | 0;
afterLineNumber = afterLineNumber | 0;
ordinal = ordinal | 0;
heightInPx = heightInPx | 0;
this._heights.splice(insertIndex, 0, heightInPx);
this._ids.splice(insertIndex, 0, id);
this._afterLineNumbers.splice(insertIndex, 0, afterLineNumber);
this._ordinals.splice(insertIndex, 0, ordinal);
this._prefixSum.splice(insertIndex, 0, 0);
let keys = Object.keys(this._whitespaceId2Index);
for (let i = 0, len = keys.length; i < len; i++) {
let sid = keys[i];
let oldIndex = this._whitespaceId2Index[sid];
if (oldIndex >= insertIndex) {
this._whitespaceId2Index[sid] = oldIndex + 1;
}
}
this._whitespaceId2Index[id.toString()] = insertIndex;
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);
}
/**
* Change properties associated with a certain whitespace.
*/
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
id = id | 0;
newAfterLineNumber = newAfterLineNumber | 0;
newHeight = newHeight | 0;
let hasChanges = false;
hasChanges = this.changeWhitespaceHeight(id, newHeight) || hasChanges;
hasChanges = this.changeWhitespaceAfterLineNumber(id, newAfterLineNumber) || hasChanges;
return hasChanges;
}
/**
* Change the height of an existing whitespace
*
* @param id The whitespace to change
* @param newHeightInPx The new height of the whitespace, in pixels
* @return Returns true if the whitespace is found and if the new height is different than the old height
*/
public changeWhitespaceHeight(id: number, newHeightInPx: number): boolean {
id = id | 0;
newHeightInPx = newHeightInPx | 0;
let sid = id.toString();
if (this._whitespaceId2Index.hasOwnProperty(sid)) {
let index = this._whitespaceId2Index[sid];
if (this._heights[index] !== newHeightInPx) {
this._heights[index] = newHeightInPx;
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);
return true;
}
}
return false;
}
/**
* Change the line number after which an existing whitespace flows.
*
* @param id The whitespace to change
* @param newAfterLineNumber The new line number the whitespace will follow
* @return Returns true if the whitespace is found and if the new line number is different than the old line number
*/
public changeWhitespaceAfterLineNumber(id: number, newAfterLineNumber: number): boolean {
id = id | 0;
newAfterLineNumber = newAfterLineNumber | 0;
let sid = id.toString();
if (this._whitespaceId2Index.hasOwnProperty(sid)) {
let index = this._whitespaceId2Index[sid];
if (this._afterLineNumbers[index] !== newAfterLineNumber) {
// `afterLineNumber` changed for this whitespace
// Record old ordinal
let ordinal = this._ordinals[index];
// Record old height
let heightInPx = this._heights[index];
// Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace
this.removeWhitespace(id);
// And add it again
let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, newAfterLineNumber, this._ordinals, ordinal);
this._insertWhitespaceAtIndex(id, insertionIndex, newAfterLineNumber, ordinal, heightInPx);
return true;
}
}
return false;
}
/**
* Remove an existing whitespace.
*
* @param id The whitespace to remove
* @return Returns true if the whitespace is found and it is removed.
*/
public removeWhitespace(id: number): boolean {
id = id | 0;
let sid = id.toString();
if (this._whitespaceId2Index.hasOwnProperty(sid)) {
let index = this._whitespaceId2Index[sid];
delete this._whitespaceId2Index[sid];
this._removeWhitespaceAtIndex(index);
return true;
}
return false;
}
private _removeWhitespaceAtIndex(removeIndex: number): void {
removeIndex = removeIndex | 0;
this._heights.splice(removeIndex, 1);
this._ids.splice(removeIndex, 1);
this._afterLineNumbers.splice(removeIndex, 1);
this._ordinals.splice(removeIndex, 1);
this._prefixSum.splice(removeIndex, 1);
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1);
let keys = Object.keys(this._whitespaceId2Index);
for (let i = 0, len = keys.length; i < len; i++) {
let sid = keys[i];
let oldIndex = this._whitespaceId2Index[sid];
if (oldIndex >= removeIndex) {
this._whitespaceId2Index[sid] = oldIndex - 1;
}
}
}
/**
* Notify the computer that lines have been deleted (a continuous zone of lines).
* This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic.
*
* @param fromLineNumber The line number at which the deletion started, inclusive
* @param toLineNumber The line number at which the deletion ended, inclusive
*/
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
fromLineNumber = fromLineNumber | 0;
toLineNumber = toLineNumber | 0;
for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) {
let afterLineNumber = this._afterLineNumbers[i];
if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) {
// The line this whitespace was after has been deleted
// => move whitespace to before first deleted line
this._afterLineNumbers[i] = fromLineNumber - 1;
} else if (afterLineNumber > toLineNumber) {
// The line this whitespace was after has been moved up
// => move whitespace up
this._afterLineNumbers[i] -= (toLineNumber - fromLineNumber + 1);
}
}
}
/**
* Notify the computer that lines have been inserted (a continuous zone of lines).
* This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic.
*
* @param fromLineNumber The line number at which the insertion started, inclusive
* @param toLineNumber The line number at which the insertion ended, inclusive.
*/
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
fromLineNumber = fromLineNumber | 0;
toLineNumber = toLineNumber | 0;
for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) {
let afterLineNumber = this._afterLineNumbers[i];
if (fromLineNumber <= afterLineNumber) {
this._afterLineNumbers[i] += (toLineNumber - fromLineNumber + 1);
}
}
}
/**
* Get the sum of all the whitespaces.
*/
public getTotalHeight(): number {
if (this._heights.length === 0) {
return 0;
}
return this.getAccumulatedHeight(this._heights.length - 1);
}
/**
* Return the sum of the heights of the whitespaces at [0..index].
* This includes the whitespace at `index`.
*
* @param index The index of the whitespace.
* @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`.
*/
public getAccumulatedHeight(index: number): number {
index = index | 0;
let startIndex = Math.max(0, this._prefixSumValidIndex + 1);
if (startIndex === 0) {
this._prefixSum[0] = this._heights[0];
startIndex++;
}
for (let i = startIndex; i <= index; i++) {
this._prefixSum[i] = this._prefixSum[i - 1] + this._heights[i];
}
this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index);
return this._prefixSum[index];
}
/**
* Find all whitespaces with `afterLineNumber` < `lineNumber` and return the sum of their heights.
*
* @param lineNumber The line number whitespaces should be before.
* @return The sum of the heights of the whitespaces before `lineNumber`.
*/
public getAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
if (lastWhitespaceBeforeLineNumber === -1) {
return 0;
}
return this.getAccumulatedHeight(lastWhitespaceBeforeLineNumber);
}
private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
// Find the whitespace before line number
let afterLineNumbers = this._afterLineNumbers;
let low = 0;
let high = afterLineNumbers.length - 1;
while (low <= high) {
let delta = (high - low) | 0;
let halfDelta = (delta / 2) | 0;
let mid = (low + halfDelta) | 0;
if (afterLineNumbers[mid] < lineNumber) {
if (mid + 1 >= afterLineNumbers.length || afterLineNumbers[mid + 1] >= lineNumber) {
return mid;
} else {
low = (mid + 1) | 0;
}
} else {
high = (mid - 1) | 0;
}
}
return -1;
}
private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
let firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1;
if (firstWhitespaceAfterLineNumber < this._heights.length) {
return firstWhitespaceAfterLineNumber;
}
return -1;
}
/**
* Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`.
* @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found.
*/
public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number {
lineNumber = lineNumber | 0;
return this._findFirstWhitespaceAfterLineNumber(lineNumber);
}
/**
* The number of whitespaces.
*/
public getCount(): number {
return this._heights.length;
}
/**
* Get the `afterLineNumber` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `afterLineNumber` of whitespace at `index`.
*/
public getAfterLineNumberForWhitespaceIndex(index: number): number {
index = index | 0;
return this._afterLineNumbers[index];
}
/**
* Get the `id` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `id` of whitespace at `index`.
*/
public getIdForWhitespaceIndex(index: number): number {
index = index | 0;
return this._ids[index];
}
/**
* Get the `height` for whitespace at index `index`.
*
* @param index The index of the whitespace.
* @return `height` of whitespace at `index`.
*/
public getHeightForWhitespaceIndex(index: number): number {
index = index | 0;
return this._heights[index];
}
/**
* Get all whitespaces.
*/
public getWhitespaces(deviceLineHeight: number): IEditorWhitespace[] {
deviceLineHeight = deviceLineHeight | 0;
let result: IEditorWhitespace[] = [];
for (let i = 0; i < this._heights.length; i++) {
result.push({
id: this._ids[i],
afterLineNumber: this._afterLineNumbers[i],
heightInLines: this._heights[i] / deviceLineHeight
});
}
return result;
}
}