src/vs/editor/common/modelLineProjectionData.ts
346 LOC · 266 covered · 80 uncovered · 44 ranges · 50 concepts · 12 introducers · 27 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
modelLineProjectionData.ts ×18
* 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 { WrappingIndent } from './config/editorOptions.js';
import { FontInfo } from './config/fontInfo.js';
import { Position } from './core/position.js';
import { InjectedTextCursorStops, InjectedTextOptions, PositionAffinity } from './model.js';
import { LineInjectedText } from './textModelEvents.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(
/**
* `injectionOptions.length` must equal `injectionOffsets.length`
*/
public injectionOptions: InjectedTextOptions[] | null,
/**
* Refers to offsets after applying injections to the source.
* The last break offset indicates the length of the source after applying injections.
*/
public breakOffsets: number[],
/**
* Refers to offsets after applying injections
*/
public breakOffsetsVisibleColumn: number[],
public wrappedTextIndentLength: number
) {
}
public getOutputLineCount(): number {
}
public getMinOutputOffset(outputLineIndex: number): number {
if (outputLineIndex > 0) {
return this.wrappedTextIndentLength;
}
return 0;
}
public getLineLength(outputLineIndex: number): number {
// 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;
}
public getMaxOutputOffset(outputLineIndex: number): number {
return this.getLineLength(outputLineIndex);
}
public translateToInputOffset(outputLineIndex: number, outputOffset: number): number {
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] + this.injectionOptions![i].content.length) {
offsetInInput = this.injectionOffsets[i];
offsetInInput -= this.injectionOptions![i].content.length;
}
} else {
break;
}
}
return offsetInInput;
}
public translateToOutputPosition(inputOffset: number, affinity: PositionAffinity = PositionAffinity.None): OutputPosition {
if (this.injectionOffsets !== null) {
if (inputOffset < this.injectionOffsets[i]) {
break;
}
if (affinity !== PositionAffinity.Right && inputOffset === this.injectionOffsets[i]) {
break;
}
inputOffsetInInputWithInjection += this.injectionOptions![i].content.length;
}
}
return this.offsetInInputWithInjectionsToOutputPosition(inputOffsetInInputWithInjection, affinity);
}
private offsetInInputWithInjectionsToOutputPosition(offsetInInputWithInjections: number, affinity: PositionAffinity = PositionAffinity.None): OutputPosition {
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 === PositionAffinity.Left) {
high = mid - 1;
} else if (offsetInInputWithInjections > midStop) {
low = mid + 1;
} else {
break;
}
if (offsetInInputWithInjections < midStart) {
low = mid + 1;
} else {
break;
}
}
}
let outputOffset = offsetInInputWithInjections - midStart;
if (mid > 0) {
outputOffset += this.wrappedTextIndentLength;
}
return new OutputPosition(mid, outputOffset);
}
public normalizeOutputPosition(outputLineIndex: number, outputOffset: number, affinity: PositionAffinity): OutputPosition {
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 === PositionAffinity.Left) {
if (outputLineIndex > 0 && outputOffset === this.getMinOutputOffset(outputLineIndex)) {
return new OutputPosition(outputLineIndex - 1, this.getMaxOutputOffset(outputLineIndex - 1));
}
}
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);
}
private outputPositionToOffsetInInputWithInjections(outputLineIndex: number, outputOffset: number): number {
outputOffset = Math.max(0, outputOffset - this.wrappedTextIndentLength);
}
const result = (outputLineIndex > 0 ? this.breakOffsets[outputLineIndex - 1] : 0) + outputOffset;
return result;
}
private normalizeOffsetInInputWithInjectionsAroundInjections(offsetInInputWithInjections: number, affinity: PositionAffinity): number {
const injectedText = this.getInjectedTextAtOffset(offsetInInputWithInjections);
modelLineProjectionData.ts ×10
if (!injectedText) {
return offsetInInputWithInjections;
}
if (affinity === 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 === PositionAffinity.Right || affinity === PositionAffinity.RightOfInjectedText) {
modelLineProjectionData.ts ×10
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++;
}
} else if (affinity === PositionAffinity.Left || affinity === 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);
public getInjectedText(outputLineIndex: number, outputOffset: number): InjectedText | null {
const offset = this.outputPositionToOffsetInInputWithInjections(outputLineIndex, outputOffset);
const injectedText = this.getInjectedTextAtOffset(offset);
if (!injectedText) {
return null;
}
return {
options: this.injectionOptions![injectedText.injectedTextIndex]
};
}
private getInjectedTextAtOffset(offsetInInputWithInjections: number): { injectedTextIndex: number; offsetInInputWithInjections: number; length: number } | undefined {
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: InjectedTextCursorStops | null | undefined): boolean {
if (cursorStop === null || cursorStop === undefined) { return true; }
return cursorStop === InjectedTextCursorStops.Right || cursorStop === InjectedTextCursorStops.Both;
}
function hasLeftCursorStop(cursorStop: InjectedTextCursorStops | null | undefined): boolean {
if (cursorStop === null || cursorStop === undefined) { return true; }
return cursorStop === InjectedTextCursorStops.Left || cursorStop === InjectedTextCursorStops.Both;
}
export class InjectedText {
constructor(public readonly options: InjectedTextOptions) { }
}
export class OutputPosition {
outputLineIndex: number;
outputOffset: number;
constructor(outputLineIndex: number, outputOffset: number) {
this.outputOffset = outputOffset;
}
toString(): string {
}
toPosition(baseLineNumber: number): Position {
return new Position(baseLineNumber + this.outputLineIndex, this.outputOffset + 1);
}
export interface ILineBreaksComputerContext {
getLineContent(lineNumber: number): string;
getLineInjectedText(lineNumber: number): LineInjectedText[] | null;
}
export interface ILineBreaksComputerFactory {
createLineBreaksComputer(context: ILineBreaksComputerContext, fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', wrapOnEscapedLineFeeds: boolean): ILineBreaksComputer;
}
export interface ILineBreaksComputer {
/**
* Pass in `previousLineBreakData` if the only difference is in breaking columns!!!
*/
addRequest(lineNumber: number, previousLineBreakData: ModelLineProjectionData | null): void;
finalize(): (ModelLineProjectionData | null)[];
}