feat: 添加vscode编辑器
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
export class LineDecoration {
|
||||
constructor(startColumn, endColumn, className, type) {
|
||||
this.startColumn = startColumn;
|
||||
this.endColumn = endColumn;
|
||||
this.className = className;
|
||||
this.type = type;
|
||||
this._lineDecorationBrand = undefined;
|
||||
}
|
||||
static _equals(a, b) {
|
||||
return (a.startColumn === b.startColumn
|
||||
&& a.endColumn === b.endColumn
|
||||
&& a.className === b.className
|
||||
&& a.type === b.type);
|
||||
}
|
||||
static equalsArr(a, b) {
|
||||
const aLen = a.length;
|
||||
const 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;
|
||||
}
|
||||
static extractWrapped(arr, startOffset, endOffset) {
|
||||
if (arr.length === 0) {
|
||||
return arr;
|
||||
}
|
||||
const startColumn = startOffset + 1;
|
||||
const endColumn = endOffset + 1;
|
||||
const lineLength = endOffset - startOffset;
|
||||
const r = [];
|
||||
let rLength = 0;
|
||||
for (const dec of arr) {
|
||||
if (dec.endColumn <= startColumn || dec.startColumn >= endColumn) {
|
||||
continue;
|
||||
}
|
||||
r[rLength++] = new LineDecoration(Math.max(1, dec.startColumn - startColumn + 1), Math.min(lineLength + 1, dec.endColumn - startColumn + 1), dec.className, dec.type);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
static filter(lineDecorations, lineNumber, minLineColumn, maxLineColumn) {
|
||||
if (lineDecorations.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const result = [];
|
||||
let resultLen = 0;
|
||||
for (let i = 0, len = lineDecorations.length; i < len; i++) {
|
||||
const d = lineDecorations[i];
|
||||
const range = d.range;
|
||||
if (range.endLineNumber < lineNumber || range.startLineNumber > lineNumber) {
|
||||
// Ignore decorations that sit outside this line
|
||||
continue;
|
||||
}
|
||||
if (range.isEmpty() && (d.type === 0 /* InlineDecorationType.Regular */ || d.type === 3 /* InlineDecorationType.RegularAffectingLetterSpacing */)) {
|
||||
// Ignore empty range decorations
|
||||
continue;
|
||||
}
|
||||
const startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn);
|
||||
const endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn);
|
||||
result[resultLen++] = new LineDecoration(startColumn, endColumn, d.inlineClassName, d.type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static _typeCompare(a, b) {
|
||||
const ORDER = [2, 0, 1, 3];
|
||||
return ORDER[a] - ORDER[b];
|
||||
}
|
||||
static compare(a, b) {
|
||||
if (a.startColumn !== b.startColumn) {
|
||||
return a.startColumn - b.startColumn;
|
||||
}
|
||||
if (a.endColumn !== b.endColumn) {
|
||||
return a.endColumn - b.endColumn;
|
||||
}
|
||||
const typeCmp = LineDecoration._typeCompare(a.type, b.type);
|
||||
if (typeCmp !== 0) {
|
||||
return typeCmp;
|
||||
}
|
||||
if (a.className !== b.className) {
|
||||
return a.className < b.className ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
export class DecorationSegment {
|
||||
constructor(startOffset, endOffset, className, metadata) {
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
this.className = className;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
||||
class Stack {
|
||||
constructor() {
|
||||
this.stopOffsets = [];
|
||||
this.classNames = [];
|
||||
this.metadata = [];
|
||||
this.count = 0;
|
||||
}
|
||||
static _metadata(metadata) {
|
||||
let result = 0;
|
||||
for (let i = 0, len = metadata.length; i < len; i++) {
|
||||
result |= metadata[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
consumeLowerThan(maxStopOffset, nextStartOffset, result) {
|
||||
while (this.count > 0 && this.stopOffsets[0] < maxStopOffset) {
|
||||
let 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(' '), Stack._metadata(this.metadata)));
|
||||
nextStartOffset = this.stopOffsets[i] + 1;
|
||||
// Consume them
|
||||
this.stopOffsets.splice(0, i + 1);
|
||||
this.classNames.splice(0, i + 1);
|
||||
this.metadata.splice(0, i + 1);
|
||||
this.count -= (i + 1);
|
||||
}
|
||||
if (this.count > 0 && nextStartOffset < maxStopOffset) {
|
||||
result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' '), Stack._metadata(this.metadata)));
|
||||
nextStartOffset = maxStopOffset;
|
||||
}
|
||||
return nextStartOffset;
|
||||
}
|
||||
insert(stopOffset, className, metadata) {
|
||||
if (this.count === 0 || this.stopOffsets[this.count - 1] <= stopOffset) {
|
||||
// Insert at the end
|
||||
this.stopOffsets.push(stopOffset);
|
||||
this.classNames.push(className);
|
||||
this.metadata.push(metadata);
|
||||
}
|
||||
else {
|
||||
// Find the insertion position for `stopOffset`
|
||||
for (let i = 0; i < this.count; i++) {
|
||||
if (this.stopOffsets[i] >= stopOffset) {
|
||||
this.stopOffsets.splice(i, 0, stopOffset);
|
||||
this.classNames.splice(i, 0, className);
|
||||
this.metadata.splice(i, 0, metadata);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.count++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
export class LineDecorationsNormalizer {
|
||||
/**
|
||||
* Normalize line decorations. Overlapping decorations will generate multiple segments
|
||||
*/
|
||||
static normalize(lineContent, lineDecorations) {
|
||||
if (lineDecorations.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const result = [];
|
||||
const stack = new Stack();
|
||||
let nextStartOffset = 0;
|
||||
for (let i = 0, len = lineDecorations.length; i < len; i++) {
|
||||
const d = lineDecorations[i];
|
||||
let startColumn = d.startColumn;
|
||||
let endColumn = d.endColumn;
|
||||
const className = d.className;
|
||||
const metadata = (d.type === 1 /* InlineDecorationType.Before */
|
||||
? 2 /* LinePartMetadata.PSEUDO_BEFORE */
|
||||
: d.type === 2 /* InlineDecorationType.After */
|
||||
? 4 /* LinePartMetadata.PSEUDO_AFTER */
|
||||
: 0);
|
||||
// If the position would end up in the middle of a high-low surrogate pair, we move it to before the pair
|
||||
if (startColumn > 1) {
|
||||
const charCodeBefore = lineContent.charCodeAt(startColumn - 2);
|
||||
if (strings.isHighSurrogate(charCodeBefore)) {
|
||||
startColumn--;
|
||||
}
|
||||
}
|
||||
if (endColumn > 1) {
|
||||
const charCodeBefore = lineContent.charCodeAt(endColumn - 2);
|
||||
if (strings.isHighSurrogate(charCodeBefore)) {
|
||||
endColumn--;
|
||||
}
|
||||
}
|
||||
const currentStartOffset = startColumn - 1;
|
||||
const currentEndOffset = endColumn - 2;
|
||||
nextStartOffset = stack.consumeLowerThan(currentStartOffset, nextStartOffset, result);
|
||||
if (stack.count === 0) {
|
||||
nextStartOffset = currentStartOffset;
|
||||
}
|
||||
stack.insert(currentEndOffset, className, metadata);
|
||||
}
|
||||
stack.consumeLowerThan(1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */, nextStartOffset, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
export class LinePart {
|
||||
constructor(
|
||||
/**
|
||||
* last char index of this token (not inclusive).
|
||||
*/
|
||||
endIndex, type, metadata, containsRTL) {
|
||||
this.endIndex = endIndex;
|
||||
this.type = type;
|
||||
this.metadata = metadata;
|
||||
this.containsRTL = containsRTL;
|
||||
this._linePartBrand = undefined;
|
||||
}
|
||||
isWhitespace() {
|
||||
return (this.metadata & 1 /* LinePartMetadata.IS_WHITESPACE_MASK */ ? true : false);
|
||||
}
|
||||
isPseudoAfter() {
|
||||
return (this.metadata & 4 /* LinePartMetadata.PSEUDO_AFTER_MASK */ ? true : false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,767 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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) => {
|
||||
const 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, includeViewZones = false) {
|
||||
this._checkPendingChanges();
|
||||
lineNumber = lineNumber | 0;
|
||||
let previousLinesHeight;
|
||||
if (lineNumber > 1) {
|
||||
previousLinesHeight = this._lineHeight * (lineNumber - 1);
|
||||
}
|
||||
else {
|
||||
previousLinesHeight = 0;
|
||||
}
|
||||
const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber - (includeViewZones ? 1 : 0));
|
||||
return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;
|
||||
}
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones = false) {
|
||||
this._checkPendingChanges();
|
||||
lineNumber = lineNumber | 0;
|
||||
const previousLinesHeight = this._lineHeight * lineNumber;
|
||||
const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber + (includeViewZones ? 1 : 0));
|
||||
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 [];
|
||||
}
|
||||
const 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;
|
||||
@@ -0,0 +1,339 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Emitter } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { Scrollable } from '../../../base/common/scrollable.js';
|
||||
import { LinesLayout } from './linesLayout.js';
|
||||
import { Viewport } from '../viewModel.js';
|
||||
import { ContentSizeChangedEvent } from '../viewModelEventDispatcher.js';
|
||||
const SMOOTH_SCROLLING_TIME = 125;
|
||||
class EditorScrollDimensions {
|
||||
constructor(width, contentWidth, height, contentHeight) {
|
||||
width = width | 0;
|
||||
contentWidth = contentWidth | 0;
|
||||
height = height | 0;
|
||||
contentHeight = contentHeight | 0;
|
||||
if (width < 0) {
|
||||
width = 0;
|
||||
}
|
||||
if (contentWidth < 0) {
|
||||
contentWidth = 0;
|
||||
}
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
if (contentHeight < 0) {
|
||||
contentHeight = 0;
|
||||
}
|
||||
this.width = width;
|
||||
this.contentWidth = contentWidth;
|
||||
this.scrollWidth = Math.max(width, contentWidth);
|
||||
this.height = height;
|
||||
this.contentHeight = contentHeight;
|
||||
this.scrollHeight = Math.max(height, contentHeight);
|
||||
}
|
||||
equals(other) {
|
||||
return (this.width === other.width
|
||||
&& this.contentWidth === other.contentWidth
|
||||
&& this.height === other.height
|
||||
&& this.contentHeight === other.contentHeight);
|
||||
}
|
||||
}
|
||||
class EditorScrollable extends Disposable {
|
||||
constructor(smoothScrollDuration, scheduleAtNextAnimationFrame) {
|
||||
super();
|
||||
this._onDidContentSizeChange = this._register(new Emitter());
|
||||
this.onDidContentSizeChange = this._onDidContentSizeChange.event;
|
||||
this._dimensions = new EditorScrollDimensions(0, 0, 0, 0);
|
||||
this._scrollable = this._register(new Scrollable({
|
||||
forceIntegerValues: true,
|
||||
smoothScrollDuration,
|
||||
scheduleAtNextAnimationFrame
|
||||
}));
|
||||
this.onDidScroll = this._scrollable.onScroll;
|
||||
}
|
||||
getScrollable() {
|
||||
return this._scrollable;
|
||||
}
|
||||
setSmoothScrollDuration(smoothScrollDuration) {
|
||||
this._scrollable.setSmoothScrollDuration(smoothScrollDuration);
|
||||
}
|
||||
validateScrollPosition(scrollPosition) {
|
||||
return this._scrollable.validateScrollPosition(scrollPosition);
|
||||
}
|
||||
getScrollDimensions() {
|
||||
return this._dimensions;
|
||||
}
|
||||
setScrollDimensions(dimensions) {
|
||||
if (this._dimensions.equals(dimensions)) {
|
||||
return;
|
||||
}
|
||||
const oldDimensions = this._dimensions;
|
||||
this._dimensions = dimensions;
|
||||
this._scrollable.setScrollDimensions({
|
||||
width: dimensions.width,
|
||||
scrollWidth: dimensions.scrollWidth,
|
||||
height: dimensions.height,
|
||||
scrollHeight: dimensions.scrollHeight
|
||||
}, true);
|
||||
const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth);
|
||||
const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight);
|
||||
if (contentWidthChanged || contentHeightChanged) {
|
||||
this._onDidContentSizeChange.fire(new ContentSizeChangedEvent(oldDimensions.contentWidth, oldDimensions.contentHeight, dimensions.contentWidth, dimensions.contentHeight));
|
||||
}
|
||||
}
|
||||
getFutureScrollPosition() {
|
||||
return this._scrollable.getFutureScrollPosition();
|
||||
}
|
||||
getCurrentScrollPosition() {
|
||||
return this._scrollable.getCurrentScrollPosition();
|
||||
}
|
||||
setScrollPositionNow(update) {
|
||||
this._scrollable.setScrollPositionNow(update);
|
||||
}
|
||||
setScrollPositionSmooth(update) {
|
||||
this._scrollable.setScrollPositionSmooth(update);
|
||||
}
|
||||
}
|
||||
export class ViewLayout extends Disposable {
|
||||
constructor(configuration, lineCount, scheduleAtNextAnimationFrame) {
|
||||
super();
|
||||
this._configuration = configuration;
|
||||
const options = this._configuration.options;
|
||||
const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
|
||||
const padding = options.get(77 /* EditorOption.padding */);
|
||||
this._linesLayout = new LinesLayout(lineCount, options.get(61 /* EditorOption.lineHeight */), padding.top, padding.bottom);
|
||||
this._scrollable = this._register(new EditorScrollable(0, scheduleAtNextAnimationFrame));
|
||||
this._configureSmoothScrollDuration();
|
||||
this._scrollable.setScrollDimensions(new EditorScrollDimensions(layoutInfo.contentWidth, 0, layoutInfo.height, 0));
|
||||
this.onDidScroll = this._scrollable.onDidScroll;
|
||||
this.onDidContentSizeChange = this._scrollable.onDidContentSizeChange;
|
||||
this._updateHeight();
|
||||
}
|
||||
dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
getScrollable() {
|
||||
return this._scrollable.getScrollable();
|
||||
}
|
||||
onHeightMaybeChanged() {
|
||||
this._updateHeight();
|
||||
}
|
||||
_configureSmoothScrollDuration() {
|
||||
this._scrollable.setSmoothScrollDuration(this._configuration.options.get(105 /* EditorOption.smoothScrolling */) ? SMOOTH_SCROLLING_TIME : 0);
|
||||
}
|
||||
// ---- begin view event handlers
|
||||
onConfigurationChanged(e) {
|
||||
const options = this._configuration.options;
|
||||
if (e.hasChanged(61 /* EditorOption.lineHeight */)) {
|
||||
this._linesLayout.setLineHeight(options.get(61 /* EditorOption.lineHeight */));
|
||||
}
|
||||
if (e.hasChanged(77 /* EditorOption.padding */)) {
|
||||
const padding = options.get(77 /* EditorOption.padding */);
|
||||
this._linesLayout.setPadding(padding.top, padding.bottom);
|
||||
}
|
||||
if (e.hasChanged(133 /* EditorOption.layoutInfo */)) {
|
||||
const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
|
||||
const width = layoutInfo.contentWidth;
|
||||
const height = layoutInfo.height;
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
const contentWidth = scrollDimensions.contentWidth;
|
||||
this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
|
||||
}
|
||||
else {
|
||||
this._updateHeight();
|
||||
}
|
||||
if (e.hasChanged(105 /* EditorOption.smoothScrolling */)) {
|
||||
this._configureSmoothScrollDuration();
|
||||
}
|
||||
}
|
||||
onFlushed(lineCount) {
|
||||
this._linesLayout.onFlushed(lineCount);
|
||||
}
|
||||
onLinesDeleted(fromLineNumber, toLineNumber) {
|
||||
this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber);
|
||||
}
|
||||
onLinesInserted(fromLineNumber, toLineNumber) {
|
||||
this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber);
|
||||
}
|
||||
// ---- end view event handlers
|
||||
_getHorizontalScrollbarHeight(width, scrollWidth) {
|
||||
const options = this._configuration.options;
|
||||
const scrollbar = options.get(94 /* EditorOption.scrollbar */);
|
||||
if (scrollbar.horizontal === 2 /* ScrollbarVisibility.Hidden */) {
|
||||
// horizontal scrollbar not visible
|
||||
return 0;
|
||||
}
|
||||
if (width >= scrollWidth) {
|
||||
// horizontal scrollbar not visible
|
||||
return 0;
|
||||
}
|
||||
return scrollbar.horizontalScrollbarSize;
|
||||
}
|
||||
_getContentHeight(width, height, contentWidth) {
|
||||
const options = this._configuration.options;
|
||||
let result = this._linesLayout.getLinesTotalHeight();
|
||||
if (options.get(96 /* EditorOption.scrollBeyondLastLine */)) {
|
||||
result += Math.max(0, height - options.get(61 /* EditorOption.lineHeight */) - options.get(77 /* EditorOption.padding */).bottom);
|
||||
}
|
||||
else {
|
||||
result += this._getHorizontalScrollbarHeight(width, contentWidth);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
_updateHeight() {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
const width = scrollDimensions.width;
|
||||
const height = scrollDimensions.height;
|
||||
const contentWidth = scrollDimensions.contentWidth;
|
||||
this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
|
||||
}
|
||||
// ---- Layouting logic
|
||||
getCurrentViewport() {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
|
||||
return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
|
||||
}
|
||||
getFutureViewport() {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
const currentScrollPosition = this._scrollable.getFutureScrollPosition();
|
||||
return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
|
||||
}
|
||||
_computeContentWidth(maxLineWidth) {
|
||||
const options = this._configuration.options;
|
||||
const wrappingInfo = options.get(134 /* EditorOption.wrappingInfo */);
|
||||
const fontInfo = options.get(46 /* EditorOption.fontInfo */);
|
||||
const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
|
||||
if (wrappingInfo.isViewportWrapping) {
|
||||
const minimap = options.get(67 /* EditorOption.minimap */);
|
||||
if (maxLineWidth > layoutInfo.contentWidth + fontInfo.typicalHalfwidthCharacterWidth) {
|
||||
// This is a case where viewport wrapping is on, but the line extends above the viewport
|
||||
if (minimap.enabled && minimap.side === 'right') {
|
||||
// We need to accomodate the scrollbar width
|
||||
return maxLineWidth + layoutInfo.verticalScrollbarWidth;
|
||||
}
|
||||
}
|
||||
return maxLineWidth;
|
||||
}
|
||||
else {
|
||||
const extraHorizontalSpace = options.get(95 /* EditorOption.scrollBeyondLastColumn */) * fontInfo.typicalHalfwidthCharacterWidth;
|
||||
const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth();
|
||||
return Math.max(maxLineWidth + extraHorizontalSpace + layoutInfo.verticalScrollbarWidth, whitespaceMinWidth);
|
||||
}
|
||||
}
|
||||
setMaxLineWidth(maxLineWidth) {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
// const newScrollWidth = ;
|
||||
this._scrollable.setScrollDimensions(new EditorScrollDimensions(scrollDimensions.width, this._computeContentWidth(maxLineWidth), scrollDimensions.height, scrollDimensions.contentHeight));
|
||||
// The height might depend on the fact that there is a horizontal scrollbar or not
|
||||
this._updateHeight();
|
||||
}
|
||||
// ---- view state
|
||||
saveState() {
|
||||
const currentScrollPosition = this._scrollable.getFutureScrollPosition();
|
||||
const scrollTop = currentScrollPosition.scrollTop;
|
||||
const firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
|
||||
const whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport);
|
||||
return {
|
||||
scrollTop: scrollTop,
|
||||
scrollTopWithoutViewZones: scrollTop - whitespaceAboveFirstLine,
|
||||
scrollLeft: currentScrollPosition.scrollLeft
|
||||
};
|
||||
}
|
||||
// ----
|
||||
changeWhitespace(callback) {
|
||||
const hadAChange = this._linesLayout.changeWhitespace(callback);
|
||||
if (hadAChange) {
|
||||
this.onHeightMaybeChanged();
|
||||
}
|
||||
return hadAChange;
|
||||
}
|
||||
getVerticalOffsetForLineNumber(lineNumber, includeViewZones = false) {
|
||||
return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones);
|
||||
}
|
||||
getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones = false) {
|
||||
return this._linesLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones);
|
||||
}
|
||||
isAfterLines(verticalOffset) {
|
||||
return this._linesLayout.isAfterLines(verticalOffset);
|
||||
}
|
||||
isInTopPadding(verticalOffset) {
|
||||
return this._linesLayout.isInTopPadding(verticalOffset);
|
||||
}
|
||||
isInBottomPadding(verticalOffset) {
|
||||
return this._linesLayout.isInBottomPadding(verticalOffset);
|
||||
}
|
||||
getLineNumberAtVerticalOffset(verticalOffset) {
|
||||
return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset);
|
||||
}
|
||||
getWhitespaceAtVerticalOffset(verticalOffset) {
|
||||
return this._linesLayout.getWhitespaceAtVerticalOffset(verticalOffset);
|
||||
}
|
||||
getLinesViewportData() {
|
||||
const visibleBox = this.getCurrentViewport();
|
||||
return this._linesLayout.getLinesViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
|
||||
}
|
||||
getLinesViewportDataAtScrollTop(scrollTop) {
|
||||
// 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);
|
||||
}
|
||||
getWhitespaceViewportData() {
|
||||
const visibleBox = this.getCurrentViewport();
|
||||
return this._linesLayout.getWhitespaceViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
|
||||
}
|
||||
getWhitespaces() {
|
||||
return this._linesLayout.getWhitespaces();
|
||||
}
|
||||
// ----
|
||||
getContentWidth() {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
return scrollDimensions.contentWidth;
|
||||
}
|
||||
getScrollWidth() {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
return scrollDimensions.scrollWidth;
|
||||
}
|
||||
getContentHeight() {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
return scrollDimensions.contentHeight;
|
||||
}
|
||||
getScrollHeight() {
|
||||
const scrollDimensions = this._scrollable.getScrollDimensions();
|
||||
return scrollDimensions.scrollHeight;
|
||||
}
|
||||
getCurrentScrollLeft() {
|
||||
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
|
||||
return currentScrollPosition.scrollLeft;
|
||||
}
|
||||
getCurrentScrollTop() {
|
||||
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
|
||||
return currentScrollPosition.scrollTop;
|
||||
}
|
||||
validateScrollPosition(scrollPosition) {
|
||||
return this._scrollable.validateScrollPosition(scrollPosition);
|
||||
}
|
||||
setScrollPosition(position, type) {
|
||||
if (type === 1 /* ScrollType.Immediate */) {
|
||||
this._scrollable.setScrollPositionNow(position);
|
||||
}
|
||||
else {
|
||||
this._scrollable.setScrollPositionSmooth(position);
|
||||
}
|
||||
}
|
||||
deltaScrollNow(deltaScrollLeft, deltaScrollTop) {
|
||||
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
|
||||
this._scrollable.setScrollPositionNow({
|
||||
scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft,
|
||||
scrollTop: currentScrollPosition.scrollTop + deltaScrollTop
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,893 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import { createStringBuilder } from '../core/stringBuilder.js';
|
||||
import { LineDecoration, LineDecorationsNormalizer } from './lineDecorations.js';
|
||||
import { LinePart } from './linePart.js';
|
||||
export class LineRange {
|
||||
constructor(startIndex, endIndex) {
|
||||
this.startOffset = startIndex;
|
||||
this.endOffset = endIndex;
|
||||
}
|
||||
equals(otherLineRange) {
|
||||
return this.startOffset === otherLineRange.startOffset
|
||||
&& this.endOffset === otherLineRange.endOffset;
|
||||
}
|
||||
}
|
||||
export class RenderLineInput {
|
||||
constructor(useMonospaceOptimizations, canUseHalfwidthRightwardsArrow, lineContent, continuesWithWrappedLine, isBasicASCII, containsRTL, fauxIndentLength, lineTokens, lineDecorations, tabSize, startVisibleColumn, spaceWidth, middotWidth, wsmiddotWidth, stopRenderingLineAfter, renderWhitespace, renderControlCharacters, fontLigatures, selectionsOnLine) {
|
||||
this.useMonospaceOptimizations = useMonospaceOptimizations;
|
||||
this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow;
|
||||
this.lineContent = lineContent;
|
||||
this.continuesWithWrappedLine = continuesWithWrappedLine;
|
||||
this.isBasicASCII = isBasicASCII;
|
||||
this.containsRTL = containsRTL;
|
||||
this.fauxIndentLength = fauxIndentLength;
|
||||
this.lineTokens = lineTokens;
|
||||
this.lineDecorations = lineDecorations.sort(LineDecoration.compare);
|
||||
this.tabSize = tabSize;
|
||||
this.startVisibleColumn = startVisibleColumn;
|
||||
this.spaceWidth = spaceWidth;
|
||||
this.stopRenderingLineAfter = stopRenderingLineAfter;
|
||||
this.renderWhitespace = (renderWhitespace === 'all'
|
||||
? 4 /* RenderWhitespace.All */
|
||||
: renderWhitespace === 'boundary'
|
||||
? 1 /* RenderWhitespace.Boundary */
|
||||
: renderWhitespace === 'selection'
|
||||
? 2 /* RenderWhitespace.Selection */
|
||||
: renderWhitespace === 'trailing'
|
||||
? 3 /* RenderWhitespace.Trailing */
|
||||
: 0 /* RenderWhitespace.None */);
|
||||
this.renderControlCharacters = renderControlCharacters;
|
||||
this.fontLigatures = fontLigatures;
|
||||
this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.startOffset < b.startOffset ? -1 : 1);
|
||||
const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth);
|
||||
const middotDiff = Math.abs(middotWidth - spaceWidth);
|
||||
if (wsmiddotDiff < middotDiff) {
|
||||
this.renderSpaceWidth = wsmiddotWidth;
|
||||
this.renderSpaceCharCode = 0x2E31; // U+2E31 - WORD SEPARATOR MIDDLE DOT
|
||||
}
|
||||
else {
|
||||
this.renderSpaceWidth = middotWidth;
|
||||
this.renderSpaceCharCode = 0xB7; // U+00B7 - MIDDLE DOT
|
||||
}
|
||||
}
|
||||
sameSelection(otherSelections) {
|
||||
if (this.selectionsOnLine === null) {
|
||||
return otherSelections === null;
|
||||
}
|
||||
if (otherSelections === null) {
|
||||
return false;
|
||||
}
|
||||
if (otherSelections.length !== this.selectionsOnLine.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < this.selectionsOnLine.length; i++) {
|
||||
if (!this.selectionsOnLine[i].equals(otherSelections[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
equals(other) {
|
||||
return (this.useMonospaceOptimizations === other.useMonospaceOptimizations
|
||||
&& this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
|
||||
&& this.lineContent === other.lineContent
|
||||
&& this.continuesWithWrappedLine === other.continuesWithWrappedLine
|
||||
&& this.isBasicASCII === other.isBasicASCII
|
||||
&& this.containsRTL === other.containsRTL
|
||||
&& this.fauxIndentLength === other.fauxIndentLength
|
||||
&& this.tabSize === other.tabSize
|
||||
&& this.startVisibleColumn === other.startVisibleColumn
|
||||
&& this.spaceWidth === other.spaceWidth
|
||||
&& this.renderSpaceWidth === other.renderSpaceWidth
|
||||
&& this.renderSpaceCharCode === other.renderSpaceCharCode
|
||||
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
|
||||
&& this.renderWhitespace === other.renderWhitespace
|
||||
&& this.renderControlCharacters === other.renderControlCharacters
|
||||
&& this.fontLigatures === other.fontLigatures
|
||||
&& LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations)
|
||||
&& this.lineTokens.equals(other.lineTokens)
|
||||
&& this.sameSelection(other.selectionsOnLine));
|
||||
}
|
||||
}
|
||||
export class DomPosition {
|
||||
constructor(partIndex, charIndex) {
|
||||
this.partIndex = partIndex;
|
||||
this.charIndex = charIndex;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Provides a both direction mapping between a line's character and its rendered position.
|
||||
*/
|
||||
export class CharacterMapping {
|
||||
constructor(length, partCount) {
|
||||
this.length = length;
|
||||
this._data = new Uint32Array(this.length);
|
||||
this._horizontalOffset = new Uint32Array(this.length);
|
||||
}
|
||||
static getPartIndex(partData) {
|
||||
return (partData & 4294901760 /* CharacterMappingConstants.PART_INDEX_MASK */) >>> 16 /* CharacterMappingConstants.PART_INDEX_OFFSET */;
|
||||
}
|
||||
static getCharIndex(partData) {
|
||||
return (partData & 65535 /* CharacterMappingConstants.CHAR_INDEX_MASK */) >>> 0 /* CharacterMappingConstants.CHAR_INDEX_OFFSET */;
|
||||
}
|
||||
setColumnInfo(column, partIndex, charIndex, horizontalOffset) {
|
||||
const partData = ((partIndex << 16 /* CharacterMappingConstants.PART_INDEX_OFFSET */)
|
||||
| (charIndex << 0 /* CharacterMappingConstants.CHAR_INDEX_OFFSET */)) >>> 0;
|
||||
this._data[column - 1] = partData;
|
||||
this._horizontalOffset[column - 1] = horizontalOffset;
|
||||
}
|
||||
getHorizontalOffset(column) {
|
||||
if (this._horizontalOffset.length === 0) {
|
||||
// No characters on this line
|
||||
return 0;
|
||||
}
|
||||
return this._horizontalOffset[column - 1];
|
||||
}
|
||||
charOffsetToPartData(charOffset) {
|
||||
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];
|
||||
}
|
||||
getDomPosition(column) {
|
||||
const partData = this.charOffsetToPartData(column - 1);
|
||||
const partIndex = CharacterMapping.getPartIndex(partData);
|
||||
const charIndex = CharacterMapping.getCharIndex(partData);
|
||||
return new DomPosition(partIndex, charIndex);
|
||||
}
|
||||
getColumn(domPosition, partLength) {
|
||||
const charOffset = this.partDataToCharOffset(domPosition.partIndex, partLength, domPosition.charIndex);
|
||||
return charOffset + 1;
|
||||
}
|
||||
partDataToCharOffset(partIndex, partLength, charIndex) {
|
||||
if (this.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const searchEntry = ((partIndex << 16 /* CharacterMappingConstants.PART_INDEX_OFFSET */)
|
||||
| (charIndex << 0 /* CharacterMappingConstants.CHAR_INDEX_OFFSET */)) >>> 0;
|
||||
let min = 0;
|
||||
let max = this.length - 1;
|
||||
while (min + 1 < max) {
|
||||
const mid = ((min + max) >>> 1);
|
||||
const midEntry = this._data[mid];
|
||||
if (midEntry === searchEntry) {
|
||||
return mid;
|
||||
}
|
||||
else if (midEntry > searchEntry) {
|
||||
max = mid;
|
||||
}
|
||||
else {
|
||||
min = mid;
|
||||
}
|
||||
}
|
||||
if (min === max) {
|
||||
return min;
|
||||
}
|
||||
const minEntry = this._data[min];
|
||||
const maxEntry = this._data[max];
|
||||
if (minEntry === searchEntry) {
|
||||
return min;
|
||||
}
|
||||
if (maxEntry === searchEntry) {
|
||||
return max;
|
||||
}
|
||||
const minPartIndex = CharacterMapping.getPartIndex(minEntry);
|
||||
const minCharIndex = CharacterMapping.getCharIndex(minEntry);
|
||||
const maxPartIndex = CharacterMapping.getPartIndex(maxEntry);
|
||||
let maxCharIndex;
|
||||
if (minPartIndex !== maxPartIndex) {
|
||||
// sitting between parts
|
||||
maxCharIndex = partLength;
|
||||
}
|
||||
else {
|
||||
maxCharIndex = CharacterMapping.getCharIndex(maxEntry);
|
||||
}
|
||||
const minEntryDistance = charIndex - minCharIndex;
|
||||
const maxEntryDistance = maxCharIndex - charIndex;
|
||||
if (minEntryDistance <= maxEntryDistance) {
|
||||
return min;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
export class RenderLineOutput {
|
||||
constructor(characterMapping, containsRTL, containsForeignElements) {
|
||||
this._renderLineOutputBrand = undefined;
|
||||
this.characterMapping = characterMapping;
|
||||
this.containsRTL = containsRTL;
|
||||
this.containsForeignElements = containsForeignElements;
|
||||
}
|
||||
}
|
||||
export function renderViewLine(input, sb) {
|
||||
if (input.lineContent.length === 0) {
|
||||
if (input.lineDecorations.length > 0) {
|
||||
// This line is empty, but it contains inline decorations
|
||||
sb.appendASCIIString(`<span>`);
|
||||
let beforeCount = 0;
|
||||
let afterCount = 0;
|
||||
let containsForeignElements = 0 /* ForeignElementType.None */;
|
||||
for (const lineDecoration of input.lineDecorations) {
|
||||
if (lineDecoration.type === 1 /* InlineDecorationType.Before */ || lineDecoration.type === 2 /* InlineDecorationType.After */) {
|
||||
sb.appendASCIIString(`<span class="`);
|
||||
sb.appendASCIIString(lineDecoration.className);
|
||||
sb.appendASCIIString(`"></span>`);
|
||||
if (lineDecoration.type === 1 /* InlineDecorationType.Before */) {
|
||||
containsForeignElements |= 1 /* ForeignElementType.Before */;
|
||||
beforeCount++;
|
||||
}
|
||||
if (lineDecoration.type === 2 /* InlineDecorationType.After */) {
|
||||
containsForeignElements |= 2 /* ForeignElementType.After */;
|
||||
afterCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.appendASCIIString(`</span>`);
|
||||
const characterMapping = new CharacterMapping(1, beforeCount + afterCount);
|
||||
characterMapping.setColumnInfo(1, beforeCount, 0, 0);
|
||||
return new RenderLineOutput(characterMapping, false, containsForeignElements);
|
||||
}
|
||||
// completely empty line
|
||||
sb.appendASCIIString('<span><span></span></span>');
|
||||
return new RenderLineOutput(new CharacterMapping(0, 0), false, 0 /* ForeignElementType.None */);
|
||||
}
|
||||
return _renderLine(resolveRenderLineInput(input), sb);
|
||||
}
|
||||
export class RenderLineOutput2 {
|
||||
constructor(characterMapping, html, containsRTL, containsForeignElements) {
|
||||
this.characterMapping = characterMapping;
|
||||
this.html = html;
|
||||
this.containsRTL = containsRTL;
|
||||
this.containsForeignElements = containsForeignElements;
|
||||
}
|
||||
}
|
||||
export function renderViewLine2(input) {
|
||||
const sb = createStringBuilder(10000);
|
||||
const out = renderViewLine(input, sb);
|
||||
return new RenderLineOutput2(out.characterMapping, sb.build(), out.containsRTL, out.containsForeignElements);
|
||||
}
|
||||
class ResolvedRenderLineInput {
|
||||
constructor(fontIsMonospace, canUseHalfwidthRightwardsArrow, lineContent, len, isOverflowing, parts, containsForeignElements, fauxIndentLength, tabSize, startVisibleColumn, containsRTL, spaceWidth, renderSpaceCharCode, renderWhitespace, renderControlCharacters) {
|
||||
this.fontIsMonospace = fontIsMonospace;
|
||||
this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow;
|
||||
this.lineContent = lineContent;
|
||||
this.len = len;
|
||||
this.isOverflowing = isOverflowing;
|
||||
this.parts = parts;
|
||||
this.containsForeignElements = containsForeignElements;
|
||||
this.fauxIndentLength = fauxIndentLength;
|
||||
this.tabSize = tabSize;
|
||||
this.startVisibleColumn = startVisibleColumn;
|
||||
this.containsRTL = containsRTL;
|
||||
this.spaceWidth = spaceWidth;
|
||||
this.renderSpaceCharCode = renderSpaceCharCode;
|
||||
this.renderWhitespace = renderWhitespace;
|
||||
this.renderControlCharacters = renderControlCharacters;
|
||||
//
|
||||
}
|
||||
}
|
||||
function resolveRenderLineInput(input) {
|
||||
const lineContent = input.lineContent;
|
||||
let isOverflowing;
|
||||
let len;
|
||||
if (input.stopRenderingLineAfter !== -1 && input.stopRenderingLineAfter < lineContent.length) {
|
||||
isOverflowing = true;
|
||||
len = input.stopRenderingLineAfter;
|
||||
}
|
||||
else {
|
||||
isOverflowing = false;
|
||||
len = lineContent.length;
|
||||
}
|
||||
let tokens = transformAndRemoveOverflowing(lineContent, input.containsRTL, input.lineTokens, input.fauxIndentLength, len);
|
||||
if (input.renderControlCharacters && !input.isBasicASCII) {
|
||||
// Calling `extractControlCharacters` before adding (possibly empty) line parts
|
||||
// for inline decorations. `extractControlCharacters` removes empty line parts.
|
||||
tokens = extractControlCharacters(lineContent, tokens);
|
||||
}
|
||||
if (input.renderWhitespace === 4 /* RenderWhitespace.All */ ||
|
||||
input.renderWhitespace === 1 /* RenderWhitespace.Boundary */ ||
|
||||
(input.renderWhitespace === 2 /* RenderWhitespace.Selection */ && !!input.selectionsOnLine) ||
|
||||
input.renderWhitespace === 3 /* RenderWhitespace.Trailing */) {
|
||||
tokens = _applyRenderWhitespace(input, lineContent, len, tokens);
|
||||
}
|
||||
let containsForeignElements = 0 /* ForeignElementType.None */;
|
||||
if (input.lineDecorations.length > 0) {
|
||||
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
|
||||
const lineDecoration = input.lineDecorations[i];
|
||||
if (lineDecoration.type === 3 /* InlineDecorationType.RegularAffectingLetterSpacing */) {
|
||||
// Pretend there are foreign elements... although not 100% accurate.
|
||||
containsForeignElements |= 1 /* ForeignElementType.Before */;
|
||||
}
|
||||
else if (lineDecoration.type === 1 /* InlineDecorationType.Before */) {
|
||||
containsForeignElements |= 1 /* ForeignElementType.Before */;
|
||||
}
|
||||
else if (lineDecoration.type === 2 /* InlineDecorationType.After */) {
|
||||
containsForeignElements |= 2 /* ForeignElementType.After */;
|
||||
}
|
||||
}
|
||||
tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
|
||||
}
|
||||
if (!input.containsRTL) {
|
||||
// We can never split RTL text, as it ruins the rendering
|
||||
tokens = splitLargeTokens(lineContent, tokens, !input.isBasicASCII || input.fontLigatures);
|
||||
}
|
||||
return new ResolvedRenderLineInput(input.useMonospaceOptimizations, input.canUseHalfwidthRightwardsArrow, lineContent, len, isOverflowing, tokens, containsForeignElements, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, input.containsRTL, input.spaceWidth, input.renderSpaceCharCode, 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(lineContent, lineContainsRTL, tokens, fauxIndentLength, len) {
|
||||
const result = [];
|
||||
let resultLen = 0;
|
||||
// The faux indent part of the line should have no token type
|
||||
if (fauxIndentLength > 0) {
|
||||
result[resultLen++] = new LinePart(fauxIndentLength, '', 0, false);
|
||||
}
|
||||
let startOffset = fauxIndentLength;
|
||||
for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
|
||||
const endIndex = tokens.getEndOffset(tokenIndex);
|
||||
if (endIndex <= fauxIndentLength) {
|
||||
// The faux indent part of the line should have no token type
|
||||
continue;
|
||||
}
|
||||
const type = tokens.getClassName(tokenIndex);
|
||||
if (endIndex >= len) {
|
||||
const tokenContainsRTL = (lineContainsRTL ? strings.containsRTL(lineContent.substring(startOffset, len)) : false);
|
||||
result[resultLen++] = new LinePart(len, type, 0, tokenContainsRTL);
|
||||
break;
|
||||
}
|
||||
const tokenContainsRTL = (lineContainsRTL ? strings.containsRTL(lineContent.substring(startOffset, endIndex)) : false);
|
||||
result[resultLen++] = new LinePart(endIndex, type, 0, tokenContainsRTL);
|
||||
startOffset = endIndex;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 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, tokens, onlyAtSpaces) {
|
||||
let lastTokenEndIndex = 0;
|
||||
const result = [];
|
||||
let resultLen = 0;
|
||||
if (onlyAtSpaces) {
|
||||
// Split only at spaces => we need to walk each character
|
||||
for (let i = 0, len = tokens.length; i < len; i++) {
|
||||
const token = tokens[i];
|
||||
const tokenEndIndex = token.endIndex;
|
||||
if (lastTokenEndIndex + 50 /* Constants.LongToken */ < tokenEndIndex) {
|
||||
const tokenType = token.type;
|
||||
const tokenMetadata = token.metadata;
|
||||
const tokenContainsRTL = token.containsRTL;
|
||||
let lastSpaceOffset = -1;
|
||||
let currTokenStart = lastTokenEndIndex;
|
||||
for (let j = lastTokenEndIndex; j < tokenEndIndex; j++) {
|
||||
if (lineContent.charCodeAt(j) === 32 /* CharCode.Space */) {
|
||||
lastSpaceOffset = j;
|
||||
}
|
||||
if (lastSpaceOffset !== -1 && j - currTokenStart >= 50 /* Constants.LongToken */) {
|
||||
// Split at `lastSpaceOffset` + 1
|
||||
result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType, tokenMetadata, tokenContainsRTL);
|
||||
currTokenStart = lastSpaceOffset + 1;
|
||||
lastSpaceOffset = -1;
|
||||
}
|
||||
}
|
||||
if (currTokenStart !== tokenEndIndex) {
|
||||
result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
result[resultLen++] = token;
|
||||
}
|
||||
lastTokenEndIndex = tokenEndIndex;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Split anywhere => we don't need to walk each character
|
||||
for (let i = 0, len = tokens.length; i < len; i++) {
|
||||
const token = tokens[i];
|
||||
const tokenEndIndex = token.endIndex;
|
||||
const diff = (tokenEndIndex - lastTokenEndIndex);
|
||||
if (diff > 50 /* Constants.LongToken */) {
|
||||
const tokenType = token.type;
|
||||
const tokenMetadata = token.metadata;
|
||||
const tokenContainsRTL = token.containsRTL;
|
||||
const piecesCount = Math.ceil(diff / 50 /* Constants.LongToken */);
|
||||
for (let j = 1; j < piecesCount; j++) {
|
||||
const pieceEndIndex = lastTokenEndIndex + (j * 50 /* Constants.LongToken */);
|
||||
result[resultLen++] = new LinePart(pieceEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
|
||||
}
|
||||
result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
|
||||
}
|
||||
else {
|
||||
result[resultLen++] = token;
|
||||
}
|
||||
lastTokenEndIndex = tokenEndIndex;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function isControlCharacter(charCode) {
|
||||
if (charCode < 32) {
|
||||
return (charCode !== 9 /* CharCode.Tab */);
|
||||
}
|
||||
if (charCode === 127) {
|
||||
// DEL
|
||||
return true;
|
||||
}
|
||||
if ((charCode >= 0x202A && charCode <= 0x202E)
|
||||
|| (charCode >= 0x2066 && charCode <= 0x2069)
|
||||
|| (charCode >= 0x200E && charCode <= 0x200F)
|
||||
|| charCode === 0x061C) {
|
||||
// Unicode Directional Formatting Characters
|
||||
// LRE U+202A LEFT-TO-RIGHT EMBEDDING
|
||||
// RLE U+202B RIGHT-TO-LEFT EMBEDDING
|
||||
// PDF U+202C POP DIRECTIONAL FORMATTING
|
||||
// LRO U+202D LEFT-TO-RIGHT OVERRIDE
|
||||
// RLO U+202E RIGHT-TO-LEFT OVERRIDE
|
||||
// LRI U+2066 LEFT-TO-RIGHT ISOLATE
|
||||
// RLI U+2067 RIGHT-TO-LEFT ISOLATE
|
||||
// FSI U+2068 FIRST STRONG ISOLATE
|
||||
// PDI U+2069 POP DIRECTIONAL ISOLATE
|
||||
// LRM U+200E LEFT-TO-RIGHT MARK
|
||||
// RLM U+200F RIGHT-TO-LEFT MARK
|
||||
// ALM U+061C ARABIC LETTER MARK
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function extractControlCharacters(lineContent, tokens) {
|
||||
const result = [];
|
||||
let lastLinePart = new LinePart(0, '', 0, false);
|
||||
let charOffset = 0;
|
||||
for (const token of tokens) {
|
||||
const tokenEndIndex = token.endIndex;
|
||||
for (; charOffset < tokenEndIndex; charOffset++) {
|
||||
const charCode = lineContent.charCodeAt(charOffset);
|
||||
if (isControlCharacter(charCode)) {
|
||||
if (charOffset > lastLinePart.endIndex) {
|
||||
// emit previous part if it has text
|
||||
lastLinePart = new LinePart(charOffset, token.type, token.metadata, token.containsRTL);
|
||||
result.push(lastLinePart);
|
||||
}
|
||||
lastLinePart = new LinePart(charOffset + 1, 'mtkcontrol', token.metadata, false);
|
||||
result.push(lastLinePart);
|
||||
}
|
||||
}
|
||||
if (charOffset > lastLinePart.endIndex) {
|
||||
// emit previous part if it has text
|
||||
lastLinePart = new LinePart(tokenEndIndex, token.type, token.metadata, token.containsRTL);
|
||||
result.push(lastLinePart);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Whitespace is rendered by "replacing" tokens with a special-purpose `mtkw` 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(input, lineContent, len, tokens) {
|
||||
const continuesWithWrappedLine = input.continuesWithWrappedLine;
|
||||
const fauxIndentLength = input.fauxIndentLength;
|
||||
const tabSize = input.tabSize;
|
||||
const startVisibleColumn = input.startVisibleColumn;
|
||||
const useMonospaceOptimizations = input.useMonospaceOptimizations;
|
||||
const selections = input.selectionsOnLine;
|
||||
const onlyBoundary = (input.renderWhitespace === 1 /* RenderWhitespace.Boundary */);
|
||||
const onlyTrailing = (input.renderWhitespace === 3 /* RenderWhitespace.Trailing */);
|
||||
const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth);
|
||||
const result = [];
|
||||
let resultLen = 0;
|
||||
let tokenIndex = 0;
|
||||
let tokenType = tokens[tokenIndex].type;
|
||||
let tokenContainsRTL = tokens[tokenIndex].containsRTL;
|
||||
let tokenEndIndex = tokens[tokenIndex].endIndex;
|
||||
const tokensLength = tokens.length;
|
||||
let lineIsEmptyOrWhitespace = false;
|
||||
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
|
||||
let lastNonWhitespaceIndex;
|
||||
if (firstNonWhitespaceIndex === -1) {
|
||||
lineIsEmptyOrWhitespace = true;
|
||||
firstNonWhitespaceIndex = len;
|
||||
lastNonWhitespaceIndex = len;
|
||||
}
|
||||
else {
|
||||
lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
|
||||
}
|
||||
let wasInWhitespace = false;
|
||||
let currentSelectionIndex = 0;
|
||||
let currentSelection = selections && selections[currentSelectionIndex];
|
||||
let tmpIndent = startVisibleColumn % tabSize;
|
||||
for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
|
||||
const chCode = lineContent.charCodeAt(charIndex);
|
||||
if (currentSelection && charIndex >= currentSelection.endOffset) {
|
||||
currentSelectionIndex++;
|
||||
currentSelection = selections && selections[currentSelectionIndex];
|
||||
}
|
||||
let isInWhitespace;
|
||||
if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) {
|
||||
// in leading or trailing whitespace
|
||||
isInWhitespace = true;
|
||||
}
|
||||
else if (chCode === 9 /* CharCode.Tab */) {
|
||||
// a tab character is rendered both in all and boundary cases
|
||||
isInWhitespace = true;
|
||||
}
|
||||
else if (chCode === 32 /* 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) : 0 /* CharCode.Null */);
|
||||
isInWhitespace = (nextChCode === 32 /* CharCode.Space */ || nextChCode === 9 /* CharCode.Tab */);
|
||||
}
|
||||
}
|
||||
else {
|
||||
isInWhitespace = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
isInWhitespace = false;
|
||||
}
|
||||
// If rendering whitespace on selection, check that the charIndex falls within a selection
|
||||
if (isInWhitespace && selections) {
|
||||
isInWhitespace = !!currentSelection && currentSelection.startOffset <= charIndex && currentSelection.endOffset > charIndex;
|
||||
}
|
||||
// If rendering only trailing whitespace, check that the charIndex points to trailing whitespace.
|
||||
if (isInWhitespace && onlyTrailing) {
|
||||
isInWhitespace = lineIsEmptyOrWhitespace || charIndex > lastNonWhitespaceIndex;
|
||||
}
|
||||
if (isInWhitespace && tokenContainsRTL) {
|
||||
// If the token contains RTL text, breaking it up into multiple line parts
|
||||
// to render whitespace might affect the browser's bidi layout.
|
||||
//
|
||||
// We render whitespace in such tokens only if the whitespace
|
||||
// is the leading or the trailing whitespace of the line,
|
||||
// which doesn't affect the browser's bidi layout.
|
||||
if (charIndex >= firstNonWhitespaceIndex && charIndex <= lastNonWhitespaceIndex) {
|
||||
isInWhitespace = false;
|
||||
}
|
||||
}
|
||||
if (wasInWhitespace) {
|
||||
// was in whitespace token
|
||||
if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
|
||||
// leaving whitespace token or entering a new indent
|
||||
if (generateLinePartForEachWhitespace) {
|
||||
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
|
||||
for (let i = lastEndIndex + 1; i <= charIndex; i++) {
|
||||
result[resultLen++] = new LinePart(i, 'mtkw', 1 /* LinePartMetadata.IS_WHITESPACE */, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
result[resultLen++] = new LinePart(charIndex, 'mtkw', 1 /* LinePartMetadata.IS_WHITESPACE */, false);
|
||||
}
|
||||
tmpIndent = tmpIndent % tabSize;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// was in regular token
|
||||
if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
|
||||
result[resultLen++] = new LinePart(charIndex, tokenType, 0, tokenContainsRTL);
|
||||
tmpIndent = tmpIndent % tabSize;
|
||||
}
|
||||
}
|
||||
if (chCode === 9 /* CharCode.Tab */) {
|
||||
tmpIndent = tabSize;
|
||||
}
|
||||
else if (strings.isFullWidthCharacter(chCode)) {
|
||||
tmpIndent += 2;
|
||||
}
|
||||
else {
|
||||
tmpIndent++;
|
||||
}
|
||||
wasInWhitespace = isInWhitespace;
|
||||
while (charIndex === tokenEndIndex) {
|
||||
tokenIndex++;
|
||||
if (tokenIndex < tokensLength) {
|
||||
tokenType = tokens[tokenIndex].type;
|
||||
tokenContainsRTL = tokens[tokenIndex].containsRTL;
|
||||
tokenEndIndex = tokens[tokenIndex].endIndex;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let generateWhitespace = false;
|
||||
if (wasInWhitespace) {
|
||||
// was in whitespace token
|
||||
if (continuesWithWrappedLine && onlyBoundary) {
|
||||
const lastCharCode = (len > 0 ? lineContent.charCodeAt(len - 1) : 0 /* CharCode.Null */);
|
||||
const prevCharCode = (len > 1 ? lineContent.charCodeAt(len - 2) : 0 /* CharCode.Null */);
|
||||
const isSingleTrailingSpace = (lastCharCode === 32 /* CharCode.Space */ && (prevCharCode !== 32 /* CharCode.Space */ && prevCharCode !== 9 /* CharCode.Tab */));
|
||||
if (!isSingleTrailingSpace) {
|
||||
generateWhitespace = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
generateWhitespace = true;
|
||||
}
|
||||
}
|
||||
if (generateWhitespace) {
|
||||
if (generateLinePartForEachWhitespace) {
|
||||
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
|
||||
for (let i = lastEndIndex + 1; i <= len; i++) {
|
||||
result[resultLen++] = new LinePart(i, 'mtkw', 1 /* LinePartMetadata.IS_WHITESPACE */, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
result[resultLen++] = new LinePart(len, 'mtkw', 1 /* LinePartMetadata.IS_WHITESPACE */, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
result[resultLen++] = new LinePart(len, tokenType, 0, tokenContainsRTL);
|
||||
}
|
||||
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, len, tokens, _lineDecorations) {
|
||||
_lineDecorations.sort(LineDecoration.compare);
|
||||
const lineDecorations = LineDecorationsNormalizer.normalize(lineContent, _lineDecorations);
|
||||
const lineDecorationsLen = lineDecorations.length;
|
||||
let lineDecorationIndex = 0;
|
||||
const result = [];
|
||||
let resultLen = 0;
|
||||
let lastResultEndIndex = 0;
|
||||
for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) {
|
||||
const token = tokens[tokenIndex];
|
||||
const tokenEndIndex = token.endIndex;
|
||||
const tokenType = token.type;
|
||||
const tokenMetadata = token.metadata;
|
||||
const tokenContainsRTL = token.containsRTL;
|
||||
while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) {
|
||||
const lineDecoration = lineDecorations[lineDecorationIndex];
|
||||
if (lineDecoration.startOffset > lastResultEndIndex) {
|
||||
lastResultEndIndex = lineDecoration.startOffset;
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
|
||||
}
|
||||
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, tokenMetadata | lineDecoration.metadata, tokenContainsRTL);
|
||||
lineDecorationIndex++;
|
||||
}
|
||||
else {
|
||||
// This line decoration continues on to the next token
|
||||
lastResultEndIndex = tokenEndIndex;
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata, tokenContainsRTL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tokenEndIndex > lastResultEndIndex) {
|
||||
lastResultEndIndex = tokenEndIndex;
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata, tokenContainsRTL);
|
||||
}
|
||||
}
|
||||
const lastTokenEndIndex = tokens[tokens.length - 1].endIndex;
|
||||
if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
|
||||
while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
|
||||
const lineDecoration = lineDecorations[lineDecorationIndex];
|
||||
result[resultLen++] = new LinePart(lastResultEndIndex, lineDecoration.className, lineDecoration.metadata, false);
|
||||
lineDecorationIndex++;
|
||||
}
|
||||
}
|
||||
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, sb) {
|
||||
const fontIsMonospace = input.fontIsMonospace;
|
||||
const canUseHalfwidthRightwardsArrow = input.canUseHalfwidthRightwardsArrow;
|
||||
const containsForeignElements = input.containsForeignElements;
|
||||
const lineContent = input.lineContent;
|
||||
const len = input.len;
|
||||
const isOverflowing = input.isOverflowing;
|
||||
const parts = input.parts;
|
||||
const fauxIndentLength = input.fauxIndentLength;
|
||||
const tabSize = input.tabSize;
|
||||
const startVisibleColumn = input.startVisibleColumn;
|
||||
const containsRTL = input.containsRTL;
|
||||
const spaceWidth = input.spaceWidth;
|
||||
const renderSpaceCharCode = input.renderSpaceCharCode;
|
||||
const renderWhitespace = input.renderWhitespace;
|
||||
const renderControlCharacters = input.renderControlCharacters;
|
||||
const characterMapping = new CharacterMapping(len + 1, parts.length);
|
||||
let lastCharacterMappingDefined = false;
|
||||
let charIndex = 0;
|
||||
let visibleColumn = startVisibleColumn;
|
||||
let charOffsetInPart = 0; // the character offset in the current part
|
||||
let charHorizontalOffset = 0; // the character horizontal position in terms of chars relative to line start
|
||||
let partDisplacement = 0;
|
||||
if (containsRTL) {
|
||||
sb.appendASCIIString('<span dir="ltr">');
|
||||
}
|
||||
else {
|
||||
sb.appendASCIIString('<span>');
|
||||
}
|
||||
for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
|
||||
const part = parts[partIndex];
|
||||
const partEndIndex = part.endIndex;
|
||||
const partType = part.type;
|
||||
const partContainsRTL = part.containsRTL;
|
||||
const partRendersWhitespace = (renderWhitespace !== 0 /* RenderWhitespace.None */ && part.isWhitespace());
|
||||
const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw' /*only whitespace*/ || !containsForeignElements);
|
||||
const partIsEmptyAndHasPseudoAfter = (charIndex === partEndIndex && part.isPseudoAfter());
|
||||
charOffsetInPart = 0;
|
||||
sb.appendASCIIString('<span ');
|
||||
if (partContainsRTL) {
|
||||
sb.appendASCIIString('style="unicode-bidi:isolate" ');
|
||||
}
|
||||
sb.appendASCIIString('class="');
|
||||
sb.appendASCIIString(partRendersWhitespaceWithWidth ? 'mtkz' : partType);
|
||||
sb.appendASCII(34 /* CharCode.DoubleQuote */);
|
||||
if (partRendersWhitespace) {
|
||||
let partWidth = 0;
|
||||
{
|
||||
let _charIndex = charIndex;
|
||||
let _visibleColumn = visibleColumn;
|
||||
for (; _charIndex < partEndIndex; _charIndex++) {
|
||||
const charCode = lineContent.charCodeAt(_charIndex);
|
||||
const charWidth = (charCode === 9 /* CharCode.Tab */ ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
|
||||
partWidth += charWidth;
|
||||
if (_charIndex >= fauxIndentLength) {
|
||||
_visibleColumn += charWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (partRendersWhitespaceWithWidth) {
|
||||
sb.appendASCIIString(' style="width:');
|
||||
sb.appendASCIIString(String(spaceWidth * partWidth));
|
||||
sb.appendASCIIString('px"');
|
||||
}
|
||||
sb.appendASCII(62 /* CharCode.GreaterThan */);
|
||||
for (; charIndex < partEndIndex; charIndex++) {
|
||||
characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, charHorizontalOffset);
|
||||
partDisplacement = 0;
|
||||
const charCode = lineContent.charCodeAt(charIndex);
|
||||
let producedCharacters;
|
||||
let charWidth;
|
||||
if (charCode === 9 /* CharCode.Tab */) {
|
||||
producedCharacters = (tabSize - (visibleColumn % tabSize)) | 0;
|
||||
charWidth = producedCharacters;
|
||||
if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
|
||||
sb.write1(0x2192); // RIGHTWARDS ARROW
|
||||
}
|
||||
else {
|
||||
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
|
||||
}
|
||||
for (let space = 2; space <= charWidth; space++) {
|
||||
sb.write1(0xA0); //
|
||||
}
|
||||
}
|
||||
else { // must be CharCode.Space
|
||||
producedCharacters = 2;
|
||||
charWidth = 1;
|
||||
sb.write1(renderSpaceCharCode); // · or word separator middle dot
|
||||
sb.write1(0x200C); // ZERO WIDTH NON-JOINER
|
||||
}
|
||||
charOffsetInPart += producedCharacters;
|
||||
charHorizontalOffset += charWidth;
|
||||
if (charIndex >= fauxIndentLength) {
|
||||
visibleColumn += charWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sb.appendASCII(62 /* CharCode.GreaterThan */);
|
||||
for (; charIndex < partEndIndex; charIndex++) {
|
||||
characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, charHorizontalOffset);
|
||||
partDisplacement = 0;
|
||||
const charCode = lineContent.charCodeAt(charIndex);
|
||||
let producedCharacters = 1;
|
||||
let charWidth = 1;
|
||||
switch (charCode) {
|
||||
case 9 /* CharCode.Tab */:
|
||||
producedCharacters = (tabSize - (visibleColumn % tabSize));
|
||||
charWidth = producedCharacters;
|
||||
for (let space = 1; space <= producedCharacters; space++) {
|
||||
sb.write1(0xA0); //
|
||||
}
|
||||
break;
|
||||
case 32 /* CharCode.Space */:
|
||||
sb.write1(0xA0); //
|
||||
break;
|
||||
case 60 /* CharCode.LessThan */:
|
||||
sb.appendASCIIString('<');
|
||||
break;
|
||||
case 62 /* CharCode.GreaterThan */:
|
||||
sb.appendASCIIString('>');
|
||||
break;
|
||||
case 38 /* CharCode.Ampersand */:
|
||||
sb.appendASCIIString('&');
|
||||
break;
|
||||
case 0 /* CharCode.Null */:
|
||||
if (renderControlCharacters) {
|
||||
// See https://unicode-table.com/en/blocks/control-pictures/
|
||||
sb.write1(9216);
|
||||
}
|
||||
else {
|
||||
sb.appendASCIIString('�');
|
||||
}
|
||||
break;
|
||||
case 65279 /* CharCode.UTF8_BOM */:
|
||||
case 8232 /* CharCode.LINE_SEPARATOR */:
|
||||
case 8233 /* CharCode.PARAGRAPH_SEPARATOR */:
|
||||
case 133 /* CharCode.NEXT_LINE */:
|
||||
sb.write1(0xFFFD);
|
||||
break;
|
||||
default:
|
||||
if (strings.isFullWidthCharacter(charCode)) {
|
||||
charWidth++;
|
||||
}
|
||||
// See https://unicode-table.com/en/blocks/control-pictures/
|
||||
if (renderControlCharacters && charCode < 32) {
|
||||
sb.write1(9216 + charCode);
|
||||
}
|
||||
else if (renderControlCharacters && charCode === 127) {
|
||||
// DEL
|
||||
sb.write1(9249);
|
||||
}
|
||||
else if (renderControlCharacters && isControlCharacter(charCode)) {
|
||||
sb.appendASCIIString('[U+');
|
||||
sb.appendASCIIString(to4CharHex(charCode));
|
||||
sb.appendASCIIString(']');
|
||||
producedCharacters = 8;
|
||||
charWidth = producedCharacters;
|
||||
}
|
||||
else {
|
||||
sb.write1(charCode);
|
||||
}
|
||||
}
|
||||
charOffsetInPart += producedCharacters;
|
||||
charHorizontalOffset += charWidth;
|
||||
if (charIndex >= fauxIndentLength) {
|
||||
visibleColumn += charWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (partIsEmptyAndHasPseudoAfter) {
|
||||
partDisplacement++;
|
||||
}
|
||||
else {
|
||||
partDisplacement = 0;
|
||||
}
|
||||
if (charIndex >= len && !lastCharacterMappingDefined && part.isPseudoAfter()) {
|
||||
lastCharacterMappingDefined = true;
|
||||
characterMapping.setColumnInfo(charIndex + 1, partIndex, charOffsetInPart, charHorizontalOffset);
|
||||
}
|
||||
sb.appendASCIIString('</span>');
|
||||
}
|
||||
if (!lastCharacterMappingDefined) {
|
||||
// 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.setColumnInfo(len + 1, parts.length - 1, charOffsetInPart, charHorizontalOffset);
|
||||
}
|
||||
if (isOverflowing) {
|
||||
sb.appendASCIIString('<span>…</span>');
|
||||
}
|
||||
sb.appendASCIIString('</span>');
|
||||
return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
|
||||
}
|
||||
function to4CharHex(n) {
|
||||
return n.toString(16).toUpperCase().padStart(4, '0');
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Range } from '../core/range.js';
|
||||
/**
|
||||
* Contains all data needed to render at a specific viewport.
|
||||
*/
|
||||
export class ViewportData {
|
||||
constructor(selections, partialData, whitespaceViewportData, model) {
|
||||
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));
|
||||
}
|
||||
getViewLineRenderingData(lineNumber) {
|
||||
return this._model.getViewportViewLineRenderingData(this.visibleRange, lineNumber);
|
||||
}
|
||||
getDecorationsInViewport() {
|
||||
return this._model.getDecorationsInViewport(this.visibleRange);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user