mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 11:08:31 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
204
src/vs/editor/common/viewLayout/lineDecorations.ts
Normal file
204
src/vs/editor/common/viewLayout/lineDecorations.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
475
src/vs/editor/common/viewLayout/linesLayout.ts
Normal file
475
src/vs/editor/common/viewLayout/linesLayout.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
280
src/vs/editor/common/viewLayout/viewLayout.ts
Normal file
280
src/vs/editor/common/viewLayout/viewLayout.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
759
src/vs/editor/common/viewLayout/viewLineRenderer.ts
Normal file
759
src/vs/editor/common/viewLayout/viewLineRenderer.ts
Normal 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 (→ or ·) do not have the same width as .
|
||||
* 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); // →
|
||||
insertSpacesCount--;
|
||||
}
|
||||
while (insertSpacesCount > 0) {
|
||||
sb.write1(0xA0); //
|
||||
insertSpacesCount--;
|
||||
}
|
||||
} else {
|
||||
// must be CharCode.Space
|
||||
sb.write1(0xb7); // ·
|
||||
}
|
||||
|
||||
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); //
|
||||
partContentCnt++;
|
||||
insertSpacesCount--;
|
||||
}
|
||||
break;
|
||||
|
||||
case CharCode.Space:
|
||||
sb.write1(0xA0); //
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.LessThan:
|
||||
sb.appendASCIIString('<');
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.GreaterThan:
|
||||
sb.appendASCIIString('>');
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.Ampersand:
|
||||
sb.appendASCIIString('&');
|
||||
partContentCnt++;
|
||||
break;
|
||||
|
||||
case CharCode.Null:
|
||||
sb.appendASCIIString('�');
|
||||
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>…</span>');
|
||||
}
|
||||
|
||||
sb.appendASCIIString('</span>');
|
||||
|
||||
return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
|
||||
}
|
||||
111
src/vs/editor/common/viewLayout/viewLinesViewportData.ts
Normal file
111
src/vs/editor/common/viewLayout/viewLinesViewportData.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
465
src/vs/editor/common/viewLayout/whitespaceComputer.ts
Normal file
465
src/vs/editor/common/viewLayout/whitespaceComputer.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user