Path: blob/main/src/vs/editor/common/viewLayout/linesLayout.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IEditorWhitespace, IPartialViewLinesViewportData, ILineHeightChangeAccessor, IViewWhitespaceViewportData, IWhitespaceChangeAccessor } from '../viewModel.js';6import * as strings from '../../../base/common/strings.js';7import { ICustomLineHeightData, LineHeightsManager } from './lineHeights.js';89interface IPendingChange { id: string; newAfterLineNumber: number; newHeight: number }10interface IPendingRemove { id: string }1112class PendingChanges {13private _hasPending: boolean;14private _inserts: EditorWhitespace[];15private _changes: IPendingChange[];16private _removes: IPendingRemove[];1718constructor() {19this._hasPending = false;20this._inserts = [];21this._changes = [];22this._removes = [];23}2425public insert(x: EditorWhitespace): void {26this._hasPending = true;27this._inserts.push(x);28}2930public change(x: IPendingChange): void {31this._hasPending = true;32this._changes.push(x);33}3435public remove(x: IPendingRemove): void {36this._hasPending = true;37this._removes.push(x);38}3940public commit(linesLayout: LinesLayout): void {41if (!this._hasPending) {42return;43}4445const inserts = this._inserts;46const changes = this._changes;47const removes = this._removes;4849this._hasPending = false;50this._inserts = [];51this._changes = [];52this._removes = [];5354linesLayout._commitPendingChanges(inserts, changes, removes);55}56}5758export class EditorWhitespace implements IEditorWhitespace {59public id: string;60public afterLineNumber: number;61public ordinal: number;62public height: number;63public minWidth: number;64public prefixSum: number;6566constructor(id: string, afterLineNumber: number, ordinal: number, height: number, minWidth: number) {67this.id = id;68this.afterLineNumber = afterLineNumber;69this.ordinal = ordinal;70this.height = height;71this.minWidth = minWidth;72this.prefixSum = 0;73}74}7576/**77* Layouting of objects that take vertical space (by having a height) and push down other objects.78*79* These objects are basically either text (lines) or spaces between those lines (whitespaces).80* This provides commodity operations for working with lines that contain whitespace that pushes lines lower (vertically).81*/82export class LinesLayout {8384private static INSTANCE_COUNT = 0;8586private readonly _instanceId: string;87private readonly _pendingChanges: PendingChanges;88private _lastWhitespaceId: number;89private _arr: EditorWhitespace[];90private _prefixSumValidIndex: number;91private _minWidth: number;92private _lineCount: number;93private _paddingTop: number;94private _paddingBottom: number;95private _lineHeightsManager: LineHeightsManager;9697constructor(lineCount: number, defaultLineHeight: number, paddingTop: number, paddingBottom: number, customLineHeightData: ICustomLineHeightData[]) {98this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT);99this._pendingChanges = new PendingChanges();100this._lastWhitespaceId = 0;101this._arr = [];102this._prefixSumValidIndex = -1;103this._minWidth = -1; /* marker for not being computed */104this._lineCount = lineCount;105this._paddingTop = paddingTop;106this._paddingBottom = paddingBottom;107this._lineHeightsManager = new LineHeightsManager(defaultLineHeight, customLineHeightData);108}109110/**111* Find the insertion index for a new value inside a sorted array of values.112* If the value is already present in the sorted array, the insertion index will be after the already existing value.113*/114public static findInsertionIndex(arr: EditorWhitespace[], afterLineNumber: number, ordinal: number): number {115let low = 0;116let high = arr.length;117118while (low < high) {119const mid = ((low + high) >>> 1);120121if (afterLineNumber === arr[mid].afterLineNumber) {122if (ordinal < arr[mid].ordinal) {123high = mid;124} else {125low = mid + 1;126}127} else if (afterLineNumber < arr[mid].afterLineNumber) {128high = mid;129} else {130low = mid + 1;131}132}133134return low;135}136137/**138* Change the height of a line in pixels.139*/140public setDefaultLineHeight(lineHeight: number): void {141this._lineHeightsManager.defaultLineHeight = lineHeight;142}143144/**145* Changes the padding used to calculate vertical offsets.146*/147public setPadding(paddingTop: number, paddingBottom: number): void {148this._paddingTop = paddingTop;149this._paddingBottom = paddingBottom;150}151152/**153* Set the number of lines.154*155* @param lineCount New number of lines.156*/157public onFlushed(lineCount: number, customLineHeightData: ICustomLineHeightData[]): void {158this._lineCount = lineCount;159this._lineHeightsManager = new LineHeightsManager(this._lineHeightsManager.defaultLineHeight, customLineHeightData);160}161162public changeLineHeights(callback: (accessor: ILineHeightChangeAccessor) => void): boolean {163let hadAChange = false;164try {165const accessor: ILineHeightChangeAccessor = {166insertOrChangeCustomLineHeight: (decorationId: string, startLineNumber: number, endLineNumber: number, lineHeight: number): void => {167hadAChange = true;168this._lineHeightsManager.insertOrChangeCustomLineHeight(decorationId, startLineNumber, endLineNumber, lineHeight);169},170removeCustomLineHeight: (decorationId: string): void => {171hadAChange = true;172this._lineHeightsManager.removeCustomLineHeight(decorationId);173}174};175callback(accessor);176} finally {177this._lineHeightsManager.commit();178}179return hadAChange;180}181182public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): boolean {183let hadAChange = false;184try {185const accessor: IWhitespaceChangeAccessor = {186insertWhitespace: (afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string => {187hadAChange = true;188afterLineNumber = afterLineNumber | 0;189ordinal = ordinal | 0;190heightInPx = heightInPx | 0;191minWidth = minWidth | 0;192const id = this._instanceId + (++this._lastWhitespaceId);193this._pendingChanges.insert(new EditorWhitespace(id, afterLineNumber, ordinal, heightInPx, minWidth));194return id;195},196changeOneWhitespace: (id: string, newAfterLineNumber: number, newHeight: number): void => {197hadAChange = true;198newAfterLineNumber = newAfterLineNumber | 0;199newHeight = newHeight | 0;200this._pendingChanges.change({ id, newAfterLineNumber, newHeight });201},202removeWhitespace: (id: string): void => {203hadAChange = true;204this._pendingChanges.remove({ id });205}206};207callback(accessor);208} finally {209this._pendingChanges.commit(this);210}211return hadAChange;212}213214public _commitPendingChanges(inserts: EditorWhitespace[], changes: IPendingChange[], removes: IPendingRemove[]): void {215if (inserts.length > 0 || removes.length > 0) {216this._minWidth = -1; /* marker for not being computed */217}218219if (inserts.length + changes.length + removes.length <= 1) {220// when only one thing happened, handle it "delicately"221for (const insert of inserts) {222this._insertWhitespace(insert);223}224for (const change of changes) {225this._changeOneWhitespace(change.id, change.newAfterLineNumber, change.newHeight);226}227for (const remove of removes) {228const index = this._findWhitespaceIndex(remove.id);229if (index === -1) {230continue;231}232this._removeWhitespace(index);233}234return;235}236237// simply rebuild the entire datastructure238239const toRemove = new Set<string>();240for (const remove of removes) {241toRemove.add(remove.id);242}243244const toChange = new Map<string, IPendingChange>();245for (const change of changes) {246toChange.set(change.id, change);247}248249const applyRemoveAndChange = (whitespaces: EditorWhitespace[]): EditorWhitespace[] => {250const result: EditorWhitespace[] = [];251for (const whitespace of whitespaces) {252if (toRemove.has(whitespace.id)) {253continue;254}255if (toChange.has(whitespace.id)) {256const change = toChange.get(whitespace.id)!;257whitespace.afterLineNumber = change.newAfterLineNumber;258whitespace.height = change.newHeight;259}260result.push(whitespace);261}262return result;263};264265const result = applyRemoveAndChange(this._arr).concat(applyRemoveAndChange(inserts));266result.sort((a, b) => {267if (a.afterLineNumber === b.afterLineNumber) {268return a.ordinal - b.ordinal;269}270return a.afterLineNumber - b.afterLineNumber;271});272273this._arr = result;274this._prefixSumValidIndex = -1;275}276277private _insertWhitespace(whitespace: EditorWhitespace): void {278const insertIndex = LinesLayout.findInsertionIndex(this._arr, whitespace.afterLineNumber, whitespace.ordinal);279this._arr.splice(insertIndex, 0, whitespace);280this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);281}282283private _findWhitespaceIndex(id: string): number {284const arr = this._arr;285for (let i = 0, len = arr.length; i < len; i++) {286if (arr[i].id === id) {287return i;288}289}290return -1;291}292293private _changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void {294const index = this._findWhitespaceIndex(id);295if (index === -1) {296return;297}298if (this._arr[index].height !== newHeight) {299this._arr[index].height = newHeight;300this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);301}302if (this._arr[index].afterLineNumber !== newAfterLineNumber) {303// `afterLineNumber` changed for this whitespace304305// Record old whitespace306const whitespace = this._arr[index];307308// Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace309this._removeWhitespace(index);310311whitespace.afterLineNumber = newAfterLineNumber;312313// And add it again314this._insertWhitespace(whitespace);315}316}317318private _removeWhitespace(removeIndex: number): void {319this._arr.splice(removeIndex, 1);320this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1);321}322323/**324* Notify the layouter that lines have been deleted (a continuous zone of lines).325*326* @param fromLineNumber The line number at which the deletion started, inclusive327* @param toLineNumber The line number at which the deletion ended, inclusive328*/329public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {330fromLineNumber = fromLineNumber | 0;331toLineNumber = toLineNumber | 0;332333this._lineCount -= (toLineNumber - fromLineNumber + 1);334for (let i = 0, len = this._arr.length; i < len; i++) {335const afterLineNumber = this._arr[i].afterLineNumber;336337if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) {338// The line this whitespace was after has been deleted339// => move whitespace to before first deleted line340this._arr[i].afterLineNumber = fromLineNumber - 1;341} else if (afterLineNumber > toLineNumber) {342// The line this whitespace was after has been moved up343// => move whitespace up344this._arr[i].afterLineNumber -= (toLineNumber - fromLineNumber + 1);345}346}347this._lineHeightsManager.onLinesDeleted(fromLineNumber, toLineNumber);348}349350/**351* Notify the layouter that lines have been inserted (a continuous zone of lines).352*353* @param fromLineNumber The line number at which the insertion started, inclusive354* @param toLineNumber The line number at which the insertion ended, inclusive.355*/356public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {357fromLineNumber = fromLineNumber | 0;358toLineNumber = toLineNumber | 0;359360this._lineCount += (toLineNumber - fromLineNumber + 1);361for (let i = 0, len = this._arr.length; i < len; i++) {362const afterLineNumber = this._arr[i].afterLineNumber;363364if (fromLineNumber <= afterLineNumber) {365this._arr[i].afterLineNumber += (toLineNumber - fromLineNumber + 1);366}367}368this._lineHeightsManager.onLinesInserted(fromLineNumber, toLineNumber);369}370371/**372* Get the sum of all the whitespaces.373*/374public getWhitespacesTotalHeight(): number {375if (this._arr.length === 0) {376return 0;377}378return this.getWhitespacesAccumulatedHeight(this._arr.length - 1);379}380381/**382* Return the sum of the heights of the whitespaces at [0..index].383* This includes the whitespace at `index`.384*385* @param index The index of the whitespace.386* @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`.387*/388public getWhitespacesAccumulatedHeight(index: number): number {389index = index | 0;390391let startIndex = Math.max(0, this._prefixSumValidIndex + 1);392if (startIndex === 0) {393this._arr[0].prefixSum = this._arr[0].height;394startIndex++;395}396397for (let i = startIndex; i <= index; i++) {398this._arr[i].prefixSum = this._arr[i - 1].prefixSum + this._arr[i].height;399}400this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index);401return this._arr[index].prefixSum;402}403404/**405* Get the sum of heights for all objects.406*407* @return The sum of heights for all objects.408*/409public getLinesTotalHeight(): number {410const linesHeight = this._lineHeightsManager.getAccumulatedLineHeightsIncludingLineNumber(this._lineCount);411const whitespacesHeight = this.getWhitespacesTotalHeight();412413return linesHeight + whitespacesHeight + this._paddingTop + this._paddingBottom;414}415416/**417* Returns the accumulated height of whitespaces before the given line number.418*419* @param lineNumber The line number420*/421public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number {422lineNumber = lineNumber | 0;423424const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);425426if (lastWhitespaceBeforeLineNumber === -1) {427return 0;428}429430return this.getWhitespacesAccumulatedHeight(lastWhitespaceBeforeLineNumber);431}432433private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number {434lineNumber = lineNumber | 0;435436// Find the whitespace before line number437const arr = this._arr;438let low = 0;439let high = arr.length - 1;440441while (low <= high) {442const delta = (high - low) | 0;443const halfDelta = (delta / 2) | 0;444const mid = (low + halfDelta) | 0;445446if (arr[mid].afterLineNumber < lineNumber) {447if (mid + 1 >= arr.length || arr[mid + 1].afterLineNumber >= lineNumber) {448return mid;449} else {450low = (mid + 1) | 0;451}452} else {453high = (mid - 1) | 0;454}455}456457return -1;458}459460private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number {461lineNumber = lineNumber | 0;462463const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);464const firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1;465466if (firstWhitespaceAfterLineNumber < this._arr.length) {467return firstWhitespaceAfterLineNumber;468}469470return -1;471}472473/**474* Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`.475* @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found.476*/477public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number {478lineNumber = lineNumber | 0;479480return this._findFirstWhitespaceAfterLineNumber(lineNumber);481}482483/**484* Get the vertical offset (the sum of heights for all objects above) a certain line number.485*486* @param lineNumber The line number487* @return The sum of heights for all objects above `lineNumber`.488*/489public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones = false): number {490lineNumber = lineNumber | 0;491492let previousLinesHeight: number;493if (lineNumber > 1) {494previousLinesHeight = this._lineHeightsManager.getAccumulatedLineHeightsIncludingLineNumber(lineNumber - 1);495} else {496previousLinesHeight = 0;497}498499const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber - (includeViewZones ? 1 : 0));500501return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;502}503504public getLineHeightForLineNumber(lineNumber: number): number {505return this._lineHeightsManager.heightForLineNumber(lineNumber);506}507508/**509* Get the vertical offset (the sum of heights for all objects above) a certain line number and also the line height of the line.510*511* @param lineNumber The line number512* @return The sum of heights for all objects above `lineNumber`.513*/514public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones = false): number {515lineNumber = lineNumber | 0;516const previousLinesHeight = this._lineHeightsManager.getAccumulatedLineHeightsIncludingLineNumber(lineNumber);517const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber + (includeViewZones ? 1 : 0));518return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;519}520521/**522* Returns if there is any whitespace in the document.523*/524public hasWhitespace(): boolean {525return this.getWhitespacesCount() > 0;526}527528/**529* The maximum min width for all whitespaces.530*/531public getWhitespaceMinWidth(): number {532if (this._minWidth === -1) {533let minWidth = 0;534for (let i = 0, len = this._arr.length; i < len; i++) {535minWidth = Math.max(minWidth, this._arr[i].minWidth);536}537this._minWidth = minWidth;538}539return this._minWidth;540}541542/**543* Check if `verticalOffset` is below all lines.544*/545public isAfterLines(verticalOffset: number): boolean {546const totalHeight = this.getLinesTotalHeight();547return verticalOffset > totalHeight;548}549550public isInTopPadding(verticalOffset: number): boolean {551if (this._paddingTop === 0) {552return false;553}554return (verticalOffset < this._paddingTop);555}556557public isInBottomPadding(verticalOffset: number): boolean {558if (this._paddingBottom === 0) {559return false;560}561const totalHeight = this.getLinesTotalHeight();562return (verticalOffset >= totalHeight - this._paddingBottom);563}564565/**566* Find the first line number that is at or after vertical offset `verticalOffset`.567* i.e. if getVerticalOffsetForLine(line) is x and getVerticalOffsetForLine(line + 1) is y, then568* getLineNumberAtOrAfterVerticalOffset(i) = line, x <= i < y.569*570* @param verticalOffset The vertical offset to search at.571* @return The line number at or after vertical offset `verticalOffset`.572*/573public getLineNumberAtOrAfterVerticalOffset(verticalOffset: number): number {574verticalOffset = verticalOffset | 0;575576if (verticalOffset < 0) {577return 1;578}579580const linesCount = this._lineCount | 0;581let minLineNumber = 1;582let maxLineNumber = linesCount;583584while (minLineNumber < maxLineNumber) {585const midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0;586587const lineHeight = this.getLineHeightForLineNumber(midLineNumber);588const midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0;589590if (verticalOffset >= midLineNumberVerticalOffset + lineHeight) {591// vertical offset is after mid line number592minLineNumber = midLineNumber + 1;593} else if (verticalOffset >= midLineNumberVerticalOffset) {594// Hit595return midLineNumber;596} else {597// vertical offset is before mid line number, but mid line number could still be what we're searching for598maxLineNumber = midLineNumber;599}600}601602if (minLineNumber > linesCount) {603return linesCount;604}605606return minLineNumber;607}608609/**610* Get all the lines and their relative vertical offsets that are positioned between `verticalOffset1` and `verticalOffset2`.611*612* @param verticalOffset1 The beginning of the viewport.613* @param verticalOffset2 The end of the viewport.614* @return A structure describing the lines positioned between `verticalOffset1` and `verticalOffset2`.615*/616public getLinesViewportData(verticalOffset1: number, verticalOffset2: number): IPartialViewLinesViewportData {617verticalOffset1 = verticalOffset1 | 0;618verticalOffset2 = verticalOffset2 | 0;619620// Find first line number621// We don't live in a perfect world, so the line number might start before or after verticalOffset1622const startLineNumber = this.getLineNumberAtOrAfterVerticalOffset(verticalOffset1) | 0;623const startLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(startLineNumber) | 0;624625let endLineNumber = this._lineCount | 0;626627// Also keep track of what whitespace we've got628let whitespaceIndex = this.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0;629const whitespaceCount = this.getWhitespacesCount() | 0;630let currentWhitespaceHeight: number;631let currentWhitespaceAfterLineNumber: number;632633if (whitespaceIndex === -1) {634whitespaceIndex = whitespaceCount;635currentWhitespaceAfterLineNumber = endLineNumber + 1;636currentWhitespaceHeight = 0;637} else {638currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;639currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0;640}641642let currentVerticalOffset = startLineNumberVerticalOffset;643let currentLineRelativeOffset = currentVerticalOffset;644645// IE (all versions) cannot handle units above about 1,533,908 px, so every 500k pixels bring numbers down646const STEP_SIZE = 500000;647let bigNumbersDelta = 0;648if (startLineNumberVerticalOffset >= STEP_SIZE) {649// Compute a delta that guarantees that lines are positioned at `lineHeight` increments650bigNumbersDelta = Math.floor(startLineNumberVerticalOffset / STEP_SIZE) * STEP_SIZE;651bigNumbersDelta = Math.floor(bigNumbersDelta / this._lineHeightsManager.defaultLineHeight) * this._lineHeightsManager.defaultLineHeight;652653currentLineRelativeOffset -= bigNumbersDelta;654}655656const linesOffsets: number[] = [];657658const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2;659let centeredLineNumber = -1;660661// Figure out how far the lines go662for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {663const lineHeight = this.getLineHeightForLineNumber(lineNumber);664if (centeredLineNumber === -1) {665const currentLineTop = currentVerticalOffset;666const currentLineBottom = currentVerticalOffset + lineHeight;667if ((currentLineTop <= verticalCenter && verticalCenter < currentLineBottom) || currentLineTop > verticalCenter) {668centeredLineNumber = lineNumber;669}670}671672// Count current line height in the vertical offsets673currentVerticalOffset += lineHeight;674linesOffsets[lineNumber - startLineNumber] = currentLineRelativeOffset;675676// Next line starts immediately after this one677currentLineRelativeOffset += lineHeight;678while (currentWhitespaceAfterLineNumber === lineNumber) {679// Push down next line with the height of the current whitespace680currentLineRelativeOffset += currentWhitespaceHeight;681682// Count current whitespace in the vertical offsets683currentVerticalOffset += currentWhitespaceHeight;684whitespaceIndex++;685686if (whitespaceIndex >= whitespaceCount) {687currentWhitespaceAfterLineNumber = endLineNumber + 1;688} else {689currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;690currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0;691}692}693694if (currentVerticalOffset >= verticalOffset2) {695// We have covered the entire viewport area, time to stop696endLineNumber = lineNumber;697break;698}699}700701if (centeredLineNumber === -1) {702centeredLineNumber = endLineNumber;703}704705const endLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(endLineNumber) | 0;706707let completelyVisibleStartLineNumber = startLineNumber;708let completelyVisibleEndLineNumber = endLineNumber;709710if (completelyVisibleStartLineNumber < completelyVisibleEndLineNumber) {711if (startLineNumberVerticalOffset < verticalOffset1) {712completelyVisibleStartLineNumber++;713}714}715if (completelyVisibleStartLineNumber < completelyVisibleEndLineNumber) {716const endLineHeight = this.getLineHeightForLineNumber(endLineNumber);717if (endLineNumberVerticalOffset + endLineHeight > verticalOffset2) {718completelyVisibleEndLineNumber--;719}720}721722return {723bigNumbersDelta: bigNumbersDelta,724startLineNumber: startLineNumber,725endLineNumber: endLineNumber,726relativeVerticalOffset: linesOffsets,727centeredLineNumber: centeredLineNumber,728completelyVisibleStartLineNumber: completelyVisibleStartLineNumber,729completelyVisibleEndLineNumber: completelyVisibleEndLineNumber,730lineHeight: this._lineHeightsManager.defaultLineHeight,731};732}733734public getVerticalOffsetForWhitespaceIndex(whitespaceIndex: number): number {735whitespaceIndex = whitespaceIndex | 0;736737const afterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex);738739let previousLinesHeight: number;740if (afterLineNumber >= 1) {741previousLinesHeight = this._lineHeightsManager.getAccumulatedLineHeightsIncludingLineNumber(afterLineNumber);742} else {743previousLinesHeight = 0;744}745746let previousWhitespacesHeight: number;747if (whitespaceIndex > 0) {748previousWhitespacesHeight = this.getWhitespacesAccumulatedHeight(whitespaceIndex - 1);749} else {750previousWhitespacesHeight = 0;751}752return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;753}754755public getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset: number): number {756verticalOffset = verticalOffset | 0;757758let minWhitespaceIndex = 0;759let maxWhitespaceIndex = this.getWhitespacesCount() - 1;760761if (maxWhitespaceIndex < 0) {762return -1;763}764765// Special case: nothing to be found766const maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex);767const maxWhitespaceHeight = this.getHeightForWhitespaceIndex(maxWhitespaceIndex);768if (verticalOffset >= maxWhitespaceVerticalOffset + maxWhitespaceHeight) {769return -1;770}771772while (minWhitespaceIndex < maxWhitespaceIndex) {773const midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2);774775const midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex);776const midWhitespaceHeight = this.getHeightForWhitespaceIndex(midWhitespaceIndex);777778if (verticalOffset >= midWhitespaceVerticalOffset + midWhitespaceHeight) {779// vertical offset is after whitespace780minWhitespaceIndex = midWhitespaceIndex + 1;781} else if (verticalOffset >= midWhitespaceVerticalOffset) {782// Hit783return midWhitespaceIndex;784} else {785// vertical offset is before whitespace, but midWhitespaceIndex might still be what we're searching for786maxWhitespaceIndex = midWhitespaceIndex;787}788}789return minWhitespaceIndex;790}791792/**793* Get exactly the whitespace that is layouted at `verticalOffset`.794*795* @param verticalOffset The vertical offset.796* @return Precisely the whitespace that is layouted at `verticaloffset` or null.797*/798public getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null {799verticalOffset = verticalOffset | 0;800801const candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset);802803if (candidateIndex < 0) {804return null;805}806807if (candidateIndex >= this.getWhitespacesCount()) {808return null;809}810811const candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex);812813if (candidateTop > verticalOffset) {814return null;815}816817const candidateHeight = this.getHeightForWhitespaceIndex(candidateIndex);818const candidateId = this.getIdForWhitespaceIndex(candidateIndex);819const candidateAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(candidateIndex);820821return {822id: candidateId,823afterLineNumber: candidateAfterLineNumber,824verticalOffset: candidateTop,825height: candidateHeight826};827}828829/**830* Get a list of whitespaces that are positioned between `verticalOffset1` and `verticalOffset2`.831*832* @param verticalOffset1 The beginning of the viewport.833* @param verticalOffset2 The end of the viewport.834* @return An array with all the whitespaces in the viewport. If no whitespace is in viewport, the array is empty.835*/836public getWhitespaceViewportData(verticalOffset1: number, verticalOffset2: number): IViewWhitespaceViewportData[] {837verticalOffset1 = verticalOffset1 | 0;838verticalOffset2 = verticalOffset2 | 0;839840const startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1);841const endIndex = this.getWhitespacesCount() - 1;842843if (startIndex < 0) {844return [];845}846847const result: IViewWhitespaceViewportData[] = [];848for (let i = startIndex; i <= endIndex; i++) {849const top = this.getVerticalOffsetForWhitespaceIndex(i);850const height = this.getHeightForWhitespaceIndex(i);851if (top >= verticalOffset2) {852break;853}854855result.push({856id: this.getIdForWhitespaceIndex(i),857afterLineNumber: this.getAfterLineNumberForWhitespaceIndex(i),858verticalOffset: top,859height: height860});861}862863return result;864}865866/**867* Get all whitespaces.868*/869public getWhitespaces(): IEditorWhitespace[] {870return this._arr.slice(0);871}872873/**874* The number of whitespaces.875*/876public getWhitespacesCount(): number {877return this._arr.length;878}879880/**881* Get the `id` for whitespace at index `index`.882*883* @param index The index of the whitespace.884* @return `id` of whitespace at `index`.885*/886public getIdForWhitespaceIndex(index: number): string {887index = index | 0;888889return this._arr[index].id;890}891892/**893* Get the `afterLineNumber` for whitespace at index `index`.894*895* @param index The index of the whitespace.896* @return `afterLineNumber` of whitespace at `index`.897*/898public getAfterLineNumberForWhitespaceIndex(index: number): number {899index = index | 0;900901return this._arr[index].afterLineNumber;902}903904/**905* Get the `height` for whitespace at index `index`.906*907* @param index The index of the whitespace.908* @return `height` of whitespace at `index`.909*/910public getHeightForWhitespaceIndex(index: number): number {911index = index | 0;912913return this._arr[index].height;914}915}916917918