monaco-editor-core
Version:
A browser based code editor
300 lines (299 loc) • 13.2 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { assertNever } from '../../base/common/assert.js';
import { Position } from './core/position.js';
import { InjectedTextCursorStops } from './model.js';
/**
* *input*:
* ```
* xxxxxxxxxxxxxxxxxxxxxxxxxxx
* ```
*
* -> Applying injections `[i...i]`, *inputWithInjections*:
* ```
* xxxxxx[iiiiiiiiii]xxxxxxxxxxxxxxxxx[ii]xxxx
* ```
*
* -> breaking at offsets `|` in `xxxxxx[iiiiiii|iii]xxxxxxxxxxx|xxxxxx[ii]xxxx|`:
* ```
* xxxxxx[iiiiiii
* iii]xxxxxxxxxxx
* xxxxxx[ii]xxxx
* ```
*
* -> applying wrappedTextIndentLength, *output*:
* ```
* xxxxxx[iiiiiii
* iii]xxxxxxxxxxx
* xxxxxx[ii]xxxx
* ```
*/
export class ModelLineProjectionData {
constructor(injectionOffsets,
/**
* `injectionOptions.length` must equal `injectionOffsets.length`
*/
injectionOptions,
/**
* Refers to offsets after applying injections to the source.
* The last break offset indicates the length of the source after applying injections.
*/
breakOffsets,
/**
* Refers to offsets after applying injections
*/
breakOffsetsVisibleColumn, wrappedTextIndentLength) {
this.injectionOffsets = injectionOffsets;
this.injectionOptions = injectionOptions;
this.breakOffsets = breakOffsets;
this.breakOffsetsVisibleColumn = breakOffsetsVisibleColumn;
this.wrappedTextIndentLength = wrappedTextIndentLength;
}
getOutputLineCount() {
return this.breakOffsets.length;
}
getMinOutputOffset(outputLineIndex) {
if (outputLineIndex > 0) {
return this.wrappedTextIndentLength;
}
return 0;
}
getLineLength(outputLineIndex) {
// These offsets refer to model text with injected text.
const startOffset = outputLineIndex > 0 ? this.breakOffsets[outputLineIndex - 1] : 0;
const endOffset = this.breakOffsets[outputLineIndex];
let lineLength = endOffset - startOffset;
if (outputLineIndex > 0) {
lineLength += this.wrappedTextIndentLength;
}
return lineLength;
}
getMaxOutputOffset(outputLineIndex) {
return this.getLineLength(outputLineIndex);
}
translateToInputOffset(outputLineIndex, outputOffset) {
if (outputLineIndex > 0) {
outputOffset = Math.max(0, outputOffset - this.wrappedTextIndentLength);
}
const offsetInInputWithInjection = outputLineIndex === 0 ? outputOffset : this.breakOffsets[outputLineIndex - 1] + outputOffset;
let offsetInInput = offsetInInputWithInjection;
if (this.injectionOffsets !== null) {
for (let i = 0; i < this.injectionOffsets.length; i++) {
if (offsetInInput > this.injectionOffsets[i]) {
if (offsetInInput < this.injectionOffsets[i] + this.injectionOptions[i].content.length) {
// `inputOffset` is within injected text
offsetInInput = this.injectionOffsets[i];
}
else {
offsetInInput -= this.injectionOptions[i].content.length;
}
}
else {
break;
}
}
}
return offsetInInput;
}
translateToOutputPosition(inputOffset, affinity = 2 /* PositionAffinity.None */) {
let inputOffsetInInputWithInjection = inputOffset;
if (this.injectionOffsets !== null) {
for (let i = 0; i < this.injectionOffsets.length; i++) {
if (inputOffset < this.injectionOffsets[i]) {
break;
}
if (affinity !== 1 /* PositionAffinity.Right */ && inputOffset === this.injectionOffsets[i]) {
break;
}
inputOffsetInInputWithInjection += this.injectionOptions[i].content.length;
}
}
return this.offsetInInputWithInjectionsToOutputPosition(inputOffsetInInputWithInjection, affinity);
}
offsetInInputWithInjectionsToOutputPosition(offsetInInputWithInjections, affinity = 2 /* PositionAffinity.None */) {
let low = 0;
let high = this.breakOffsets.length - 1;
let mid = 0;
let midStart = 0;
while (low <= high) {
mid = low + ((high - low) / 2) | 0;
const midStop = this.breakOffsets[mid];
midStart = mid > 0 ? this.breakOffsets[mid - 1] : 0;
if (affinity === 0 /* PositionAffinity.Left */) {
if (offsetInInputWithInjections <= midStart) {
high = mid - 1;
}
else if (offsetInInputWithInjections > midStop) {
low = mid + 1;
}
else {
break;
}
}
else {
if (offsetInInputWithInjections < midStart) {
high = mid - 1;
}
else if (offsetInInputWithInjections >= midStop) {
low = mid + 1;
}
else {
break;
}
}
}
let outputOffset = offsetInInputWithInjections - midStart;
if (mid > 0) {
outputOffset += this.wrappedTextIndentLength;
}
return new OutputPosition(mid, outputOffset);
}
normalizeOutputPosition(outputLineIndex, outputOffset, affinity) {
if (this.injectionOffsets !== null) {
const offsetInInputWithInjections = this.outputPositionToOffsetInInputWithInjections(outputLineIndex, outputOffset);
const normalizedOffsetInUnwrappedLine = this.normalizeOffsetInInputWithInjectionsAroundInjections(offsetInInputWithInjections, affinity);
if (normalizedOffsetInUnwrappedLine !== offsetInInputWithInjections) {
// injected text caused a change
return this.offsetInInputWithInjectionsToOutputPosition(normalizedOffsetInUnwrappedLine, affinity);
}
}
if (affinity === 0 /* PositionAffinity.Left */) {
if (outputLineIndex > 0 && outputOffset === this.getMinOutputOffset(outputLineIndex)) {
return new OutputPosition(outputLineIndex - 1, this.getMaxOutputOffset(outputLineIndex - 1));
}
}
else if (affinity === 1 /* PositionAffinity.Right */) {
const maxOutputLineIndex = this.getOutputLineCount() - 1;
if (outputLineIndex < maxOutputLineIndex && outputOffset === this.getMaxOutputOffset(outputLineIndex)) {
return new OutputPosition(outputLineIndex + 1, this.getMinOutputOffset(outputLineIndex + 1));
}
}
return new OutputPosition(outputLineIndex, outputOffset);
}
outputPositionToOffsetInInputWithInjections(outputLineIndex, outputOffset) {
if (outputLineIndex > 0) {
outputOffset = Math.max(0, outputOffset - this.wrappedTextIndentLength);
}
const result = (outputLineIndex > 0 ? this.breakOffsets[outputLineIndex - 1] : 0) + outputOffset;
return result;
}
normalizeOffsetInInputWithInjectionsAroundInjections(offsetInInputWithInjections, affinity) {
const injectedText = this.getInjectedTextAtOffset(offsetInInputWithInjections);
if (!injectedText) {
return offsetInInputWithInjections;
}
if (affinity === 2 /* PositionAffinity.None */) {
if (offsetInInputWithInjections === injectedText.offsetInInputWithInjections + injectedText.length
&& hasRightCursorStop(this.injectionOptions[injectedText.injectedTextIndex].cursorStops)) {
return injectedText.offsetInInputWithInjections + injectedText.length;
}
else {
let result = injectedText.offsetInInputWithInjections;
if (hasLeftCursorStop(this.injectionOptions[injectedText.injectedTextIndex].cursorStops)) {
return result;
}
let index = injectedText.injectedTextIndex - 1;
while (index >= 0 && this.injectionOffsets[index] === this.injectionOffsets[injectedText.injectedTextIndex]) {
if (hasRightCursorStop(this.injectionOptions[index].cursorStops)) {
break;
}
result -= this.injectionOptions[index].content.length;
if (hasLeftCursorStop(this.injectionOptions[index].cursorStops)) {
break;
}
index--;
}
return result;
}
}
else if (affinity === 1 /* PositionAffinity.Right */ || affinity === 4 /* PositionAffinity.RightOfInjectedText */) {
let result = injectedText.offsetInInputWithInjections + injectedText.length;
let index = injectedText.injectedTextIndex;
// traverse all injected text that touch each other
while (index + 1 < this.injectionOffsets.length && this.injectionOffsets[index + 1] === this.injectionOffsets[index]) {
result += this.injectionOptions[index + 1].content.length;
index++;
}
return result;
}
else if (affinity === 0 /* PositionAffinity.Left */ || affinity === 3 /* PositionAffinity.LeftOfInjectedText */) {
// affinity is left
let result = injectedText.offsetInInputWithInjections;
let index = injectedText.injectedTextIndex;
// traverse all injected text that touch each other
while (index - 1 >= 0 && this.injectionOffsets[index - 1] === this.injectionOffsets[index]) {
result -= this.injectionOptions[index - 1].content.length;
index--;
}
return result;
}
assertNever(affinity);
}
getInjectedText(outputLineIndex, outputOffset) {
const offset = this.outputPositionToOffsetInInputWithInjections(outputLineIndex, outputOffset);
const injectedText = this.getInjectedTextAtOffset(offset);
if (!injectedText) {
return null;
}
return {
options: this.injectionOptions[injectedText.injectedTextIndex]
};
}
getInjectedTextAtOffset(offsetInInputWithInjections) {
const injectionOffsets = this.injectionOffsets;
const injectionOptions = this.injectionOptions;
if (injectionOffsets !== null) {
let totalInjectedTextLengthBefore = 0;
for (let i = 0; i < injectionOffsets.length; i++) {
const length = injectionOptions[i].content.length;
const injectedTextStartOffsetInInputWithInjections = injectionOffsets[i] + totalInjectedTextLengthBefore;
const injectedTextEndOffsetInInputWithInjections = injectionOffsets[i] + totalInjectedTextLengthBefore + length;
if (injectedTextStartOffsetInInputWithInjections > offsetInInputWithInjections) {
// Injected text starts later.
break; // All later injected texts have an even larger offset.
}
if (offsetInInputWithInjections <= injectedTextEndOffsetInInputWithInjections) {
// Injected text ends after or with the given position (but also starts with or before it).
return {
injectedTextIndex: i,
offsetInInputWithInjections: injectedTextStartOffsetInInputWithInjections,
length
};
}
totalInjectedTextLengthBefore += length;
}
}
return undefined;
}
}
function hasRightCursorStop(cursorStop) {
if (cursorStop === null || cursorStop === undefined) {
return true;
}
return cursorStop === InjectedTextCursorStops.Right || cursorStop === InjectedTextCursorStops.Both;
}
function hasLeftCursorStop(cursorStop) {
if (cursorStop === null || cursorStop === undefined) {
return true;
}
return cursorStop === InjectedTextCursorStops.Left || cursorStop === InjectedTextCursorStops.Both;
}
export class InjectedText {
constructor(options) {
this.options = options;
}
}
export class OutputPosition {
constructor(outputLineIndex, outputOffset) {
this.outputLineIndex = outputLineIndex;
this.outputOffset = outputOffset;
}
toString() {
return `${this.outputLineIndex}:${this.outputOffset}`;
}
toPosition(baseLineNumber) {
return new Position(baseLineNumber + this.outputLineIndex, this.outputOffset + 1);
}
}