Path: blob/main/src/vs/editor/browser/viewParts/viewLines/viewLine.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as browser from '../../../../base/browser/browser.js';6import { FastDomNode, createFastDomNode } from '../../../../base/browser/fastDomNode.js';7import * as platform from '../../../../base/common/platform.js';8import { IVisibleLine } from '../../view/viewLayer.js';9import { RangeUtil } from './rangeUtil.js';10import { StringBuilder } from '../../../common/core/stringBuilder.js';11import { FloatHorizontalRange, VisibleRanges } from '../../view/renderingContext.js';12import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';13import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, DomPosition, RenderWhitespace } from '../../../common/viewLayout/viewLineRenderer.js';14import { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js';15import { isHighContrast } from '../../../../platform/theme/common/theme.js';16import { EditorFontLigatures } from '../../../common/config/editorOptions.js';17import { DomReadingContext } from './domReadingContext.js';18import type { ViewLineOptions } from './viewLineOptions.js';19import { ViewGpuContext } from '../../gpu/viewGpuContext.js';20import { OffsetRange } from '../../../common/core/ranges/offsetRange.js';21import { InlineDecorationType } from '../../../common/viewModel/inlineDecorations.js';22import { TextDirection } from '../../../common/model.js';2324const canUseFastRenderedViewLine = (function () {25if (platform.isNative) {26// In VSCode we know very well when the zoom level changes27return true;28}2930if (platform.isLinux || browser.isFirefox || browser.isSafari) {31// On Linux, it appears that zooming affects char widths (in pixels), which is unexpected.32// --33// Even though we read character widths correctly, having read them at a specific zoom level34// does not mean they are the same at the current zoom level.35// --36// This could be improved if we ever figure out how to get an event when browsers zoom,37// but until then we have to stick with reading client rects.38// --39// The same has been observed with Firefox on Windows740// --41// The same has been oversved with Safari42return false;43}4445return true;46})();4748let monospaceAssumptionsAreValid = true;4950export class ViewLine implements IVisibleLine {5152public static readonly CLASS_NAME = 'view-line';5354private _options: ViewLineOptions;55private _isMaybeInvalid: boolean;56private _renderedViewLine: IRenderedViewLine | null;5758constructor(private readonly _viewGpuContext: ViewGpuContext | undefined, options: ViewLineOptions) {59this._options = options;60this._isMaybeInvalid = true;61this._renderedViewLine = null;62}6364// --- begin IVisibleLineData6566public getDomNode(): HTMLElement | null {67if (this._renderedViewLine && this._renderedViewLine.domNode) {68return this._renderedViewLine.domNode.domNode;69}70return null;71}72public setDomNode(domNode: HTMLElement): void {73if (this._renderedViewLine) {74this._renderedViewLine.domNode = createFastDomNode(domNode);75} else {76throw new Error('I have no rendered view line to set the dom node to...');77}78}7980public onContentChanged(): void {81this._isMaybeInvalid = true;82}83public onTokensChanged(): void {84this._isMaybeInvalid = true;85}86public onDecorationsChanged(): void {87this._isMaybeInvalid = true;88}89public onOptionsChanged(newOptions: ViewLineOptions): void {90this._isMaybeInvalid = true;91this._options = newOptions;92}93public onSelectionChanged(): boolean {94if (isHighContrast(this._options.themeType) || this._renderedViewLine?.input.renderWhitespace === RenderWhitespace.Selection) {95this._isMaybeInvalid = true;96return true;97}98return false;99}100101public renderLine(lineNumber: number, deltaTop: number, lineHeight: number, viewportData: ViewportData, sb: StringBuilder): boolean {102if (this._options.useGpu && this._viewGpuContext?.canRender(this._options, viewportData, lineNumber)) {103this._renderedViewLine?.domNode?.domNode.remove();104this._renderedViewLine = null;105return false;106}107108if (this._isMaybeInvalid === false) {109// it appears that nothing relevant has changed110return false;111}112113this._isMaybeInvalid = false;114115const lineData = viewportData.getViewLineRenderingData(lineNumber);116const options = this._options;117const actualInlineDecorations = LineDecoration.filter(lineData.inlineDecorations, lineNumber, lineData.minColumn, lineData.maxColumn);118const renderWhitespace = (lineData.hasVariableFonts || options.experimentalWhitespaceRendering === 'off') ? options.renderWhitespace : 'none';119const allowFastRendering = !lineData.hasVariableFonts;120121// Only send selection information when needed for rendering whitespace122let selectionsOnLine: OffsetRange[] | null = null;123if (isHighContrast(options.themeType) || renderWhitespace === 'selection') {124const selections = viewportData.selections;125for (const selection of selections) {126127if (selection.endLineNumber < lineNumber || selection.startLineNumber > lineNumber) {128// Selection does not intersect line129continue;130}131132const startColumn = (selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn);133const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn);134135if (startColumn < endColumn) {136if (isHighContrast(options.themeType)) {137actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular));138}139if (renderWhitespace === 'selection') {140if (!selectionsOnLine) {141selectionsOnLine = [];142}143144selectionsOnLine.push(new OffsetRange(startColumn - 1, endColumn - 1));145}146}147}148}149150const renderLineInput = new RenderLineInput(151options.useMonospaceOptimizations,152options.canUseHalfwidthRightwardsArrow,153lineData.content,154lineData.continuesWithWrappedLine,155lineData.isBasicASCII,156lineData.containsRTL,157lineData.minColumn - 1,158lineData.tokens,159actualInlineDecorations,160lineData.tabSize,161lineData.startVisibleColumn,162options.spaceWidth,163options.middotWidth,164options.wsmiddotWidth,165options.stopRenderingLineAfter,166renderWhitespace,167options.renderControlCharacters,168options.fontLigatures !== EditorFontLigatures.OFF,169selectionsOnLine,170lineData.textDirection,171options.verticalScrollbarSize172);173174if (this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput)) {175// no need to do anything, we have the same render input176return false;177}178179sb.appendString('<div ');180if (lineData.textDirection === TextDirection.RTL) {181sb.appendString('dir="rtl" ');182} else if (lineData.containsRTL) {183sb.appendString('dir="ltr" ');184}185sb.appendString('style="top:');186sb.appendString(String(deltaTop));187sb.appendString('px;height:');188sb.appendString(String(lineHeight));189sb.appendString('px;line-height:');190sb.appendString(String(lineHeight));191if (lineData.textDirection === TextDirection.RTL) {192sb.appendString('px;padding-right:');193sb.appendString(String(options.verticalScrollbarSize));194}195sb.appendString('px;" class="');196sb.appendString(ViewLine.CLASS_NAME);197sb.appendString('">');198199const output = renderViewLine(renderLineInput, sb);200201sb.appendString('</div>');202203let renderedViewLine: IRenderedViewLine | null = null;204if (205allowFastRendering206&& monospaceAssumptionsAreValid207&& canUseFastRenderedViewLine208&& lineData.isBasicASCII209&& renderLineInput.isLTR210&& options.useMonospaceOptimizations211&& output.containsForeignElements === ForeignElementType.None212) {213renderedViewLine = new FastRenderedViewLine(214this._renderedViewLine ? this._renderedViewLine.domNode : null,215renderLineInput,216output.characterMapping217);218}219220if (!renderedViewLine) {221renderedViewLine = createRenderedLine(222this._renderedViewLine ? this._renderedViewLine.domNode : null,223renderLineInput,224output.characterMapping,225output.containsForeignElements226);227}228229this._renderedViewLine = renderedViewLine;230231return true;232}233234public layoutLine(lineNumber: number, deltaTop: number, lineHeight: number): void {235if (this._renderedViewLine && this._renderedViewLine.domNode) {236this._renderedViewLine.domNode.setTop(deltaTop);237this._renderedViewLine.domNode.setHeight(lineHeight);238this._renderedViewLine.domNode.setLineHeight(lineHeight);239}240}241242// --- end IVisibleLineData243244public isRenderedRTL(): boolean {245if (!this._renderedViewLine) {246return false;247}248return this._renderedViewLine.input.textDirection === TextDirection.RTL;249}250251public getWidth(context: DomReadingContext | null): number {252if (!this._renderedViewLine) {253return 0;254}255return this._renderedViewLine.getWidth(context);256}257258public getWidthIsFast(): boolean {259if (!this._renderedViewLine) {260return true;261}262return this._renderedViewLine.getWidthIsFast();263}264265public needsMonospaceFontCheck(): boolean {266if (!this._renderedViewLine) {267return false;268}269return (this._renderedViewLine instanceof FastRenderedViewLine);270}271272public monospaceAssumptionsAreValid(): boolean {273if (!this._renderedViewLine) {274return monospaceAssumptionsAreValid;275}276if (this._renderedViewLine instanceof FastRenderedViewLine) {277return this._renderedViewLine.monospaceAssumptionsAreValid();278}279return monospaceAssumptionsAreValid;280}281282public onMonospaceAssumptionsInvalidated(): void {283if (this._renderedViewLine && this._renderedViewLine instanceof FastRenderedViewLine) {284this._renderedViewLine = this._renderedViewLine.toSlowRenderedLine();285}286}287288public getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): VisibleRanges | null {289if (!this._renderedViewLine) {290return null;291}292293startColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, startColumn));294endColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, endColumn));295296const stopRenderingLineAfter = this._renderedViewLine.input.stopRenderingLineAfter;297298if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1 && endColumn > stopRenderingLineAfter + 1) {299// This range is obviously not visible300return new VisibleRanges(true, [new FloatHorizontalRange(this.getWidth(context), 0)]);301}302303if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1) {304startColumn = stopRenderingLineAfter + 1;305}306307if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter + 1) {308endColumn = stopRenderingLineAfter + 1;309}310311const horizontalRanges = this._renderedViewLine.getVisibleRangesForRange(lineNumber, startColumn, endColumn, context);312if (horizontalRanges && horizontalRanges.length > 0) {313return new VisibleRanges(false, horizontalRanges);314}315316return null;317}318319public getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number {320if (!this._renderedViewLine) {321return 1;322}323return this._renderedViewLine.getColumnOfNodeOffset(spanNode, offset);324}325}326327interface IRenderedViewLine {328domNode: FastDomNode<HTMLElement> | null;329readonly input: RenderLineInput;330getWidth(context: DomReadingContext | null): number;331getWidthIsFast(): boolean;332getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null;333getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number;334}335336const enum Constants {337/**338* It seems that rounding errors occur with long lines, so the purely multiplication based339* method is only viable for short lines. For longer lines, we look up the real position of340* every 300th character and use multiplication based on that.341*342* See https://github.com/microsoft/vscode/issues/33178343*/344MaxMonospaceDistance = 300345}346347/**348* A rendered line which is guaranteed to contain only regular ASCII and is rendered with a monospace font.349*/350class FastRenderedViewLine implements IRenderedViewLine {351352public domNode: FastDomNode<HTMLElement> | null;353public readonly input: RenderLineInput;354355private readonly _characterMapping: CharacterMapping;356private readonly _charWidth: number;357private readonly _keyColumnPixelOffsetCache: Float32Array | null;358private _cachedWidth: number = -1;359360constructor(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping) {361this.domNode = domNode;362this.input = renderLineInput;363const keyColumnCount = Math.floor(renderLineInput.lineContent.length / Constants.MaxMonospaceDistance);364if (keyColumnCount > 0) {365this._keyColumnPixelOffsetCache = new Float32Array(keyColumnCount);366for (let i = 0; i < keyColumnCount; i++) {367this._keyColumnPixelOffsetCache[i] = -1;368}369} else {370this._keyColumnPixelOffsetCache = null;371}372373this._characterMapping = characterMapping;374this._charWidth = renderLineInput.spaceWidth;375}376377public getWidth(context: DomReadingContext | null): number {378if (!this.domNode || this.input.lineContent.length < Constants.MaxMonospaceDistance) {379const horizontalOffset = this._characterMapping.getHorizontalOffset(this._characterMapping.length);380return Math.round(this._charWidth * horizontalOffset);381}382if (this._cachedWidth === -1) {383this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth;384context?.markDidDomLayout();385}386return this._cachedWidth;387}388389public getWidthIsFast(): boolean {390return (this.input.lineContent.length < Constants.MaxMonospaceDistance) || this._cachedWidth !== -1;391}392393public monospaceAssumptionsAreValid(): boolean {394if (!this.domNode) {395return monospaceAssumptionsAreValid;396}397if (this.input.lineContent.length < Constants.MaxMonospaceDistance) {398const expectedWidth = this.getWidth(null);399const actualWidth = (<HTMLSpanElement>this.domNode.domNode.firstChild).offsetWidth;400if (Math.abs(expectedWidth - actualWidth) >= 2) {401// more than 2px off402console.warn(`monospace assumptions have been violated, therefore disabling monospace optimizations!`);403monospaceAssumptionsAreValid = false;404}405}406return monospaceAssumptionsAreValid;407}408409public toSlowRenderedLine(): RenderedViewLine {410return createRenderedLine(this.domNode, this.input, this._characterMapping, ForeignElementType.None);411}412413public getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {414const startPosition = this._getColumnPixelOffset(lineNumber, startColumn, context);415const endPosition = this._getColumnPixelOffset(lineNumber, endColumn, context);416return [new FloatHorizontalRange(startPosition, endPosition - startPosition)];417}418419private _getColumnPixelOffset(lineNumber: number, column: number, context: DomReadingContext): number {420if (column <= Constants.MaxMonospaceDistance) {421const horizontalOffset = this._characterMapping.getHorizontalOffset(column);422return this._charWidth * horizontalOffset;423}424425const keyColumnOrdinal = Math.floor((column - 1) / Constants.MaxMonospaceDistance) - 1;426const keyColumn = (keyColumnOrdinal + 1) * Constants.MaxMonospaceDistance + 1;427let keyColumnPixelOffset = -1;428if (this._keyColumnPixelOffsetCache) {429keyColumnPixelOffset = this._keyColumnPixelOffsetCache[keyColumnOrdinal];430if (keyColumnPixelOffset === -1) {431keyColumnPixelOffset = this._actualReadPixelOffset(lineNumber, keyColumn, context);432this._keyColumnPixelOffsetCache[keyColumnOrdinal] = keyColumnPixelOffset;433}434}435436if (keyColumnPixelOffset === -1) {437// Could not read actual key column pixel offset438const horizontalOffset = this._characterMapping.getHorizontalOffset(column);439return this._charWidth * horizontalOffset;440}441442const keyColumnHorizontalOffset = this._characterMapping.getHorizontalOffset(keyColumn);443const horizontalOffset = this._characterMapping.getHorizontalOffset(column);444return keyColumnPixelOffset + this._charWidth * (horizontalOffset - keyColumnHorizontalOffset);445}446447private _getReadingTarget(myDomNode: FastDomNode<HTMLElement>): HTMLElement {448return <HTMLSpanElement>myDomNode.domNode.firstChild;449}450451private _actualReadPixelOffset(lineNumber: number, column: number, context: DomReadingContext): number {452if (!this.domNode) {453return -1;454}455const domPosition = this._characterMapping.getDomPosition(column);456const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(this.domNode), domPosition.partIndex, domPosition.charIndex, domPosition.partIndex, domPosition.charIndex, context);457if (!r || r.length === 0) {458return -1;459}460return r[0].left;461}462463public getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number {464return getColumnOfNodeOffset(this._characterMapping, spanNode, offset);465}466}467468/**469* Every time we render a line, we save what we have rendered in an instance of this class.470*/471class RenderedViewLine implements IRenderedViewLine {472473public domNode: FastDomNode<HTMLElement> | null;474public readonly input: RenderLineInput;475476protected readonly _characterMapping: CharacterMapping;477private readonly _isWhitespaceOnly: boolean;478private readonly _containsForeignElements: ForeignElementType;479private _cachedWidth: number;480481/**482* This is a map that is used only when the line is guaranteed to be rendered LTR and has no RTL text.483*/484private readonly _pixelOffsetCache: Float32Array | null;485486constructor(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType) {487this.domNode = domNode;488this.input = renderLineInput;489this._characterMapping = characterMapping;490this._isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent);491this._containsForeignElements = containsForeignElements;492this._cachedWidth = -1;493494this._pixelOffsetCache = null;495if (renderLineInput.isLTR) {496this._pixelOffsetCache = new Float32Array(Math.max(2, this._characterMapping.length + 1));497for (let column = 0, len = this._characterMapping.length; column <= len; column++) {498this._pixelOffsetCache[column] = -1;499}500}501}502503// --- Reading from the DOM methods504505protected _getReadingTarget(myDomNode: FastDomNode<HTMLElement>): HTMLElement {506return <HTMLSpanElement>myDomNode.domNode.firstChild;507}508509/**510* Width of the line in pixels511*/512public getWidth(context: DomReadingContext | null): number {513if (!this.domNode) {514return 0;515}516if (this._cachedWidth === -1) {517this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth;518context?.markDidDomLayout();519}520return this._cachedWidth;521}522523public getWidthIsFast(): boolean {524if (this._cachedWidth === -1) {525return false;526}527return true;528}529530/**531* Visible ranges for a model range532*/533public getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {534if (!this.domNode) {535return null;536}537if (this._pixelOffsetCache !== null) {538// the text is guaranteed to be entirely LTR539const startOffset = this._readPixelOffset(this.domNode, lineNumber, startColumn, context);540if (startOffset === -1) {541return null;542}543544const endOffset = this._readPixelOffset(this.domNode, lineNumber, endColumn, context);545if (endOffset === -1) {546return null;547}548549return [new FloatHorizontalRange(startOffset, endOffset - startOffset)];550}551552return this._readVisibleRangesForRange(this.domNode, lineNumber, startColumn, endColumn, context);553}554555protected _readVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {556if (startColumn === endColumn) {557const pixelOffset = this._readPixelOffset(domNode, lineNumber, startColumn, context);558if (pixelOffset === -1) {559return null;560} else {561return [new FloatHorizontalRange(pixelOffset, 0)];562}563} else {564return this._readRawVisibleRangesForRange(domNode, startColumn, endColumn, context);565}566}567568protected _readPixelOffset(domNode: FastDomNode<HTMLElement>, lineNumber: number, column: number, context: DomReadingContext): number {569if (this.input.isLTR && this._characterMapping.length === 0) {570// This line has no content571if (this._containsForeignElements === ForeignElementType.None) {572// We can assume the line is really empty573return 0;574}575if (this._containsForeignElements === ForeignElementType.After) {576// We have foreign elements after the (empty) line577return 0;578}579if (this._containsForeignElements === ForeignElementType.Before) {580// We have foreign elements before the (empty) line581return this.getWidth(context);582}583// We have foreign elements before & after the (empty) line584const readingTarget = this._getReadingTarget(domNode);585if (readingTarget.firstChild) {586context.markDidDomLayout();587return (<HTMLSpanElement>readingTarget.firstChild).offsetWidth;588} else {589return 0;590}591}592593if (this._pixelOffsetCache !== null) {594// the text is guaranteed to be LTR595596const cachedPixelOffset = this._pixelOffsetCache[column];597if (cachedPixelOffset !== -1) {598return cachedPixelOffset;599}600601const result = this._actualReadPixelOffset(domNode, lineNumber, column, context);602this._pixelOffsetCache[column] = result;603return result;604}605606return this._actualReadPixelOffset(domNode, lineNumber, column, context);607}608609private _actualReadPixelOffset(domNode: FastDomNode<HTMLElement>, lineNumber: number, column: number, context: DomReadingContext): number {610if (this._characterMapping.length === 0) {611// This line has no content612const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), 0, 0, 0, 0, context);613if (!r || r.length === 0) {614return -1;615}616return r[0].left;617}618619if (this.input.isLTR && column === this._characterMapping.length && this._isWhitespaceOnly && this._containsForeignElements === ForeignElementType.None) {620// This branch helps in the case of whitespace only lines which have a width set621return this.getWidth(context);622}623624const domPosition = this._characterMapping.getDomPosition(column);625626const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), domPosition.partIndex, domPosition.charIndex, domPosition.partIndex, domPosition.charIndex, context);627if (!r || r.length === 0) {628return -1;629}630const result = r[0].left;631if (this.input.isBasicASCII) {632const horizontalOffset = this._characterMapping.getHorizontalOffset(column);633const expectedResult = Math.round(this.input.spaceWidth * horizontalOffset);634if (Math.abs(expectedResult - result) <= 1) {635return expectedResult;636}637}638return result;639}640641private _readRawVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {642643if (this.input.isLTR && startColumn === 1 && endColumn === this._characterMapping.length) {644// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line645646return [new FloatHorizontalRange(0, this.getWidth(context))];647}648649const startDomPosition = this._characterMapping.getDomPosition(startColumn);650const endDomPosition = this._characterMapping.getDomPosition(endColumn);651652return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startDomPosition.partIndex, startDomPosition.charIndex, endDomPosition.partIndex, endDomPosition.charIndex, context);653}654655/**656* Returns the column for the text found at a specific offset inside a rendered dom node657*/658public getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number {659return getColumnOfNodeOffset(this._characterMapping, spanNode, offset);660}661}662663class WebKitRenderedViewLine extends RenderedViewLine {664protected override _readVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {665const output = super._readVisibleRangesForRange(domNode, lineNumber, startColumn, endColumn, context);666667if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) {668return output;669}670671// WebKit is buggy and returns an expanded range (to contain words in some cases)672// The last client rect is enlarged (I think)673if (this.input.isLTR) {674// This is an attempt to patch things up675// Find position of last column676const endPixelOffset = this._readPixelOffset(domNode, lineNumber, endColumn, context);677if (endPixelOffset !== -1) {678const lastRange = output[output.length - 1];679if (lastRange.left < endPixelOffset) {680// Trim down the width of the last visible range to not go after the last column's position681lastRange.width = endPixelOffset - lastRange.left;682}683}684}685686return output;687}688}689690const createRenderedLine: (domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType) => RenderedViewLine = (function () {691if (browser.isWebKit) {692return createWebKitRenderedLine;693}694return createNormalRenderedLine;695})();696697function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType): RenderedViewLine {698return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsForeignElements);699}700701function createNormalRenderedLine(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType): RenderedViewLine {702return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsForeignElements);703}704705export function getColumnOfNodeOffset(characterMapping: CharacterMapping, spanNode: HTMLElement, offset: number): number {706const spanNodeTextContentLength = spanNode.textContent!.length;707708let spanIndex = -1;709while (spanNode) {710spanNode = <HTMLElement>spanNode.previousSibling;711spanIndex++;712}713714return characterMapping.getColumn(new DomPosition(spanIndex, offset), spanNodeTextContentLength);715}716717718