123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as strings from '../../../base/common/strings.js';
- class PendingChanges {
- constructor() {
- this._hasPending = false;
- this._inserts = [];
- this._changes = [];
- this._removes = [];
- }
- insert(x) {
- this._hasPending = true;
- this._inserts.push(x);
- }
- change(x) {
- this._hasPending = true;
- this._changes.push(x);
- }
- remove(x) {
- this._hasPending = true;
- this._removes.push(x);
- }
- mustCommit() {
- return this._hasPending;
- }
- commit(linesLayout) {
- if (!this._hasPending) {
- return;
- }
- const inserts = this._inserts;
- const changes = this._changes;
- const removes = this._removes;
- this._hasPending = false;
- this._inserts = [];
- this._changes = [];
- this._removes = [];
- linesLayout._commitPendingChanges(inserts, changes, removes);
- }
- }
- export class EditorWhitespace {
- constructor(id, afterLineNumber, ordinal, height, minWidth) {
- this.id = id;
- this.afterLineNumber = afterLineNumber;
- this.ordinal = ordinal;
- this.height = height;
- this.minWidth = minWidth;
- this.prefixSum = 0;
- }
- }
- /**
- * 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).
- */
- export class LinesLayout {
- constructor(lineCount, lineHeight, paddingTop, paddingBottom) {
- this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT);
- this._pendingChanges = new PendingChanges();
- this._lastWhitespaceId = 0;
- this._arr = [];
- this._prefixSumValidIndex = -1;
- this._minWidth = -1; /* marker for not being computed */
- this._lineCount = lineCount;
- this._lineHeight = lineHeight;
- this._paddingTop = paddingTop;
- this._paddingBottom = paddingBottom;
- }
- /**
- * 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.
- */
- static findInsertionIndex(arr, afterLineNumber, ordinal) {
- let low = 0;
- let high = arr.length;
- while (low < high) {
- const mid = ((low + high) >>> 1);
- if (afterLineNumber === arr[mid].afterLineNumber) {
- if (ordinal < arr[mid].ordinal) {
- high = mid;
- }
- else {
- low = mid + 1;
- }
- }
- else if (afterLineNumber < arr[mid].afterLineNumber) {
- high = mid;
- }
- else {
- low = mid + 1;
- }
- }
- return low;
- }
- /**
- * Change the height of a line in pixels.
- */
- setLineHeight(lineHeight) {
- this._checkPendingChanges();
- this._lineHeight = lineHeight;
- }
- /**
- * Changes the padding used to calculate vertical offsets.
- */
- setPadding(paddingTop, paddingBottom) {
- this._paddingTop = paddingTop;
- this._paddingBottom = paddingBottom;
- }
- /**
- * Set the number of lines.
- *
- * @param lineCount New number of lines.
- */
- onFlushed(lineCount) {
- this._checkPendingChanges();
- this._lineCount = lineCount;
- }
- changeWhitespace(callback) {
- let hadAChange = false;
- try {
- const accessor = {
- insertWhitespace: (afterLineNumber, ordinal, heightInPx, minWidth) => {
- hadAChange = true;
- afterLineNumber = afterLineNumber | 0;
- ordinal = ordinal | 0;
- heightInPx = heightInPx | 0;
- minWidth = minWidth | 0;
- const id = this._instanceId + (++this._lastWhitespaceId);
- this._pendingChanges.insert(new EditorWhitespace(id, afterLineNumber, ordinal, heightInPx, minWidth));
- return id;
- },
- changeOneWhitespace: (id, newAfterLineNumber, newHeight) => {
- hadAChange = true;
- newAfterLineNumber = newAfterLineNumber | 0;
- newHeight = newHeight | 0;
- this._pendingChanges.change({ id, newAfterLineNumber, newHeight });
- },
- removeWhitespace: (id) => {
- hadAChange = true;
- this._pendingChanges.remove({ id });
- }
- };
- callback(accessor);
- }
- finally {
- this._pendingChanges.commit(this);
- }
- return hadAChange;
- }
- _commitPendingChanges(inserts, changes, removes) {
- if (inserts.length > 0 || removes.length > 0) {
- this._minWidth = -1; /* marker for not being computed */
- }
- if (inserts.length + changes.length + removes.length <= 1) {
- // when only one thing happened, handle it "delicately"
- for (const insert of inserts) {
- this._insertWhitespace(insert);
- }
- for (const change of changes) {
- this._changeOneWhitespace(change.id, change.newAfterLineNumber, change.newHeight);
- }
- for (const remove of removes) {
- const index = this._findWhitespaceIndex(remove.id);
- if (index === -1) {
- continue;
- }
- this._removeWhitespace(index);
- }
- return;
- }
- // simply rebuild the entire datastructure
- const toRemove = new Set();
- for (const remove of removes) {
- toRemove.add(remove.id);
- }
- const toChange = new Map();
- for (const change of changes) {
- toChange.set(change.id, change);
- }
- const applyRemoveAndChange = (whitespaces) => {
- let result = [];
- for (const whitespace of whitespaces) {
- if (toRemove.has(whitespace.id)) {
- continue;
- }
- if (toChange.has(whitespace.id)) {
- const change = toChange.get(whitespace.id);
- whitespace.afterLineNumber = change.newAfterLineNumber;
- whitespace.height = change.newHeight;
- }
- result.push(whitespace);
- }
- return result;
- };
- const result = applyRemoveAndChange(this._arr).concat(applyRemoveAndChange(inserts));
- result.sort((a, b) => {
- if (a.afterLineNumber === b.afterLineNumber) {
- return a.ordinal - b.ordinal;
- }
- return a.afterLineNumber - b.afterLineNumber;
- });
- this._arr = result;
- this._prefixSumValidIndex = -1;
- }
- _checkPendingChanges() {
- if (this._pendingChanges.mustCommit()) {
- this._pendingChanges.commit(this);
- }
- }
- _insertWhitespace(whitespace) {
- const insertIndex = LinesLayout.findInsertionIndex(this._arr, whitespace.afterLineNumber, whitespace.ordinal);
- this._arr.splice(insertIndex, 0, whitespace);
- this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);
- }
- _findWhitespaceIndex(id) {
- const arr = this._arr;
- for (let i = 0, len = arr.length; i < len; i++) {
- if (arr[i].id === id) {
- return i;
- }
- }
- return -1;
- }
- _changeOneWhitespace(id, newAfterLineNumber, newHeight) {
- const index = this._findWhitespaceIndex(id);
- if (index === -1) {
- return;
- }
- if (this._arr[index].height !== newHeight) {
- this._arr[index].height = newHeight;
- this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);
- }
- if (this._arr[index].afterLineNumber !== newAfterLineNumber) {
- // `afterLineNumber` changed for this whitespace
- // Record old whitespace
- const whitespace = this._arr[index];
- // Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace
- this._removeWhitespace(index);
- whitespace.afterLineNumber = newAfterLineNumber;
- // And add it again
- this._insertWhitespace(whitespace);
- }
- }
- _removeWhitespace(removeIndex) {
- this._arr.splice(removeIndex, 1);
- this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1);
- }
- /**
- * 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
- */
- onLinesDeleted(fromLineNumber, toLineNumber) {
- this._checkPendingChanges();
- fromLineNumber = fromLineNumber | 0;
- toLineNumber = toLineNumber | 0;
- this._lineCount -= (toLineNumber - fromLineNumber + 1);
- for (let i = 0, len = this._arr.length; i < len; i++) {
- const afterLineNumber = this._arr[i].afterLineNumber;
- if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) {
- // The line this whitespace was after has been deleted
- // => move whitespace to before first deleted line
- this._arr[i].afterLineNumber = fromLineNumber - 1;
- }
- else if (afterLineNumber > toLineNumber) {
- // The line this whitespace was after has been moved up
- // => move whitespace up
- this._arr[i].afterLineNumber -= (toLineNumber - fromLineNumber + 1);
- }
- }
- }
- /**
- * 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.
- */
- onLinesInserted(fromLineNumber, toLineNumber) {
- this._checkPendingChanges();
- fromLineNumber = fromLineNumber | 0;
- toLineNumber = toLineNumber | 0;
- this._lineCount += (toLineNumber - fromLineNumber + 1);
- for (let i = 0, len = this._arr.length; i < len; i++) {
- const afterLineNumber = this._arr[i].afterLineNumber;
- if (fromLineNumber <= afterLineNumber) {
- this._arr[i].afterLineNumber += (toLineNumber - fromLineNumber + 1);
- }
- }
- }
- /**
- * Get the sum of all the whitespaces.
- */
- getWhitespacesTotalHeight() {
- this._checkPendingChanges();
- if (this._arr.length === 0) {
- return 0;
- }
- return this.getWhitespacesAccumulatedHeight(this._arr.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`.
- */
- getWhitespacesAccumulatedHeight(index) {
- this._checkPendingChanges();
- index = index | 0;
- let startIndex = Math.max(0, this._prefixSumValidIndex + 1);
- if (startIndex === 0) {
- this._arr[0].prefixSum = this._arr[0].height;
- startIndex++;
- }
- for (let i = startIndex; i <= index; i++) {
- this._arr[i].prefixSum = this._arr[i - 1].prefixSum + this._arr[i].height;
- }
- this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index);
- return this._arr[index].prefixSum;
- }
- /**
- * Get the sum of heights for all objects.
- *
- * @return The sum of heights for all objects.
- */
- getLinesTotalHeight() {
- this._checkPendingChanges();
- const linesHeight = this._lineHeight * this._lineCount;
- const whitespacesHeight = this.getWhitespacesTotalHeight();
- return linesHeight + whitespacesHeight + this._paddingTop + this._paddingBottom;
- }
- /**
- * Returns the accumulated height of whitespaces before the given line number.
- *
- * @param lineNumber The line number
- */
- getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber) {
- this._checkPendingChanges();
- lineNumber = lineNumber | 0;
- const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
- if (lastWhitespaceBeforeLineNumber === -1) {
- return 0;
- }
- return this.getWhitespacesAccumulatedHeight(lastWhitespaceBeforeLineNumber);
- }
- _findLastWhitespaceBeforeLineNumber(lineNumber) {
- lineNumber = lineNumber | 0;
- // Find the whitespace before line number
- const arr = this._arr;
- let low = 0;
- let high = arr.length - 1;
- while (low <= high) {
- const delta = (high - low) | 0;
- const halfDelta = (delta / 2) | 0;
- const mid = (low + halfDelta) | 0;
- if (arr[mid].afterLineNumber < lineNumber) {
- if (mid + 1 >= arr.length || arr[mid + 1].afterLineNumber >= lineNumber) {
- return mid;
- }
- else {
- low = (mid + 1) | 0;
- }
- }
- else {
- high = (mid - 1) | 0;
- }
- }
- return -1;
- }
- _findFirstWhitespaceAfterLineNumber(lineNumber) {
- lineNumber = lineNumber | 0;
- const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
- const firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1;
- if (firstWhitespaceAfterLineNumber < this._arr.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.
- */
- getFirstWhitespaceIndexAfterLineNumber(lineNumber) {
- this._checkPendingChanges();
- lineNumber = lineNumber | 0;
- return this._findFirstWhitespaceAfterLineNumber(lineNumber);
- }
- /**
- * 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`.
- */
- getVerticalOffsetForLineNumber(lineNumber) {
- this._checkPendingChanges();
- lineNumber = lineNumber | 0;
- let previousLinesHeight;
- if (lineNumber > 1) {
- previousLinesHeight = this._lineHeight * (lineNumber - 1);
- }
- else {
- previousLinesHeight = 0;
- }
- const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber);
- return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;
- }
- /**
- * The maximum min width for all whitespaces.
- */
- getWhitespaceMinWidth() {
- this._checkPendingChanges();
- if (this._minWidth === -1) {
- let minWidth = 0;
- for (let i = 0, len = this._arr.length; i < len; i++) {
- minWidth = Math.max(minWidth, this._arr[i].minWidth);
- }
- this._minWidth = minWidth;
- }
- return this._minWidth;
- }
- /**
- * Check if `verticalOffset` is below all lines.
- */
- isAfterLines(verticalOffset) {
- this._checkPendingChanges();
- const totalHeight = this.getLinesTotalHeight();
- return verticalOffset > totalHeight;
- }
- isInTopPadding(verticalOffset) {
- if (this._paddingTop === 0) {
- return false;
- }
- this._checkPendingChanges();
- return (verticalOffset < this._paddingTop);
- }
- isInBottomPadding(verticalOffset) {
- if (this._paddingBottom === 0) {
- return false;
- }
- this._checkPendingChanges();
- const totalHeight = this.getLinesTotalHeight();
- return (verticalOffset >= totalHeight - this._paddingBottom);
- }
- /**
- * 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`.
- */
- getLineNumberAtOrAfterVerticalOffset(verticalOffset) {
- this._checkPendingChanges();
- 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) {
- const midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0;
- const 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`.
- */
- getLinesViewportData(verticalOffset1, verticalOffset2) {
- this._checkPendingChanges();
- 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.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0;
- const whitespaceCount = this.getWhitespacesCount() | 0;
- let currentWhitespaceHeight;
- let currentWhitespaceAfterLineNumber;
- if (whitespaceIndex === -1) {
- whitespaceIndex = whitespaceCount;
- currentWhitespaceAfterLineNumber = endLineNumber + 1;
- currentWhitespaceHeight = 0;
- }
- else {
- currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
- currentWhitespaceHeight = this.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;
- }
- const linesOffsets = [];
- 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) {
- const currentLineTop = currentVerticalOffset;
- const 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.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
- currentWhitespaceHeight = this.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
- };
- }
- getVerticalOffsetForWhitespaceIndex(whitespaceIndex) {
- this._checkPendingChanges();
- whitespaceIndex = whitespaceIndex | 0;
- const afterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex);
- let previousLinesHeight;
- if (afterLineNumber >= 1) {
- previousLinesHeight = this._lineHeight * afterLineNumber;
- }
- else {
- previousLinesHeight = 0;
- }
- let previousWhitespacesHeight;
- if (whitespaceIndex > 0) {
- previousWhitespacesHeight = this.getWhitespacesAccumulatedHeight(whitespaceIndex - 1);
- }
- else {
- previousWhitespacesHeight = 0;
- }
- return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;
- }
- getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset) {
- this._checkPendingChanges();
- verticalOffset = verticalOffset | 0;
- let minWhitespaceIndex = 0;
- let maxWhitespaceIndex = this.getWhitespacesCount() - 1;
- if (maxWhitespaceIndex < 0) {
- return -1;
- }
- // Special case: nothing to be found
- const maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex);
- const maxWhitespaceHeight = this.getHeightForWhitespaceIndex(maxWhitespaceIndex);
- if (verticalOffset >= maxWhitespaceVerticalOffset + maxWhitespaceHeight) {
- return -1;
- }
- while (minWhitespaceIndex < maxWhitespaceIndex) {
- const midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2);
- const midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex);
- const midWhitespaceHeight = this.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.
- */
- getWhitespaceAtVerticalOffset(verticalOffset) {
- this._checkPendingChanges();
- verticalOffset = verticalOffset | 0;
- const candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset);
- if (candidateIndex < 0) {
- return null;
- }
- if (candidateIndex >= this.getWhitespacesCount()) {
- return null;
- }
- const candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex);
- if (candidateTop > verticalOffset) {
- return null;
- }
- const candidateHeight = this.getHeightForWhitespaceIndex(candidateIndex);
- const candidateId = this.getIdForWhitespaceIndex(candidateIndex);
- const candidateAfterLineNumber = this.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.
- */
- getWhitespaceViewportData(verticalOffset1, verticalOffset2) {
- this._checkPendingChanges();
- verticalOffset1 = verticalOffset1 | 0;
- verticalOffset2 = verticalOffset2 | 0;
- const startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1);
- const endIndex = this.getWhitespacesCount() - 1;
- if (startIndex < 0) {
- return [];
- }
- let result = [];
- for (let i = startIndex; i <= endIndex; i++) {
- const top = this.getVerticalOffsetForWhitespaceIndex(i);
- const height = this.getHeightForWhitespaceIndex(i);
- if (top >= verticalOffset2) {
- break;
- }
- result.push({
- id: this.getIdForWhitespaceIndex(i),
- afterLineNumber: this.getAfterLineNumberForWhitespaceIndex(i),
- verticalOffset: top,
- height: height
- });
- }
- return result;
- }
- /**
- * Get all whitespaces.
- */
- getWhitespaces() {
- this._checkPendingChanges();
- return this._arr.slice(0);
- }
- /**
- * The number of whitespaces.
- */
- getWhitespacesCount() {
- this._checkPendingChanges();
- return this._arr.length;
- }
- /**
- * Get the `id` for whitespace at index `index`.
- *
- * @param index The index of the whitespace.
- * @return `id` of whitespace at `index`.
- */
- getIdForWhitespaceIndex(index) {
- this._checkPendingChanges();
- index = index | 0;
- return this._arr[index].id;
- }
- /**
- * Get the `afterLineNumber` for whitespace at index `index`.
- *
- * @param index The index of the whitespace.
- * @return `afterLineNumber` of whitespace at `index`.
- */
- getAfterLineNumberForWhitespaceIndex(index) {
- this._checkPendingChanges();
- index = index | 0;
- return this._arr[index].afterLineNumber;
- }
- /**
- * Get the `height` for whitespace at index `index`.
- *
- * @param index The index of the whitespace.
- * @return `height` of whitespace at `index`.
- */
- getHeightForWhitespaceIndex(index) {
- this._checkPendingChanges();
- index = index | 0;
- return this._arr[index].height;
- }
- }
- LinesLayout.INSTANCE_COUNT = 0;
|