Path: blob/main/src/vs/editor/browser/viewParts/viewLines/viewLine.ts
5251 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}325326public resetCachedWidth(): void {327this._renderedViewLine?.resetCachedWidth();328}329}330331interface IRenderedViewLine {332domNode: FastDomNode<HTMLElement> | null;333readonly input: RenderLineInput;334getWidth(context: DomReadingContext | null): number;335getWidthIsFast(): boolean;336resetCachedWidth(): void;337getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null;338getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number;339}340341const enum Constants {342/**343* It seems that rounding errors occur with long lines, so the purely multiplication based344* method is only viable for short lines. For longer lines, we look up the real position of345* every 300th character and use multiplication based on that.346*347* See https://github.com/microsoft/vscode/issues/33178348*/349MaxMonospaceDistance = 300350}351352/**353* A rendered line which is guaranteed to contain only regular ASCII and is rendered with a monospace font.354*/355class FastRenderedViewLine implements IRenderedViewLine {356357public domNode: FastDomNode<HTMLElement> | null;358public readonly input: RenderLineInput;359360private readonly _characterMapping: CharacterMapping;361private readonly _charWidth: number;362private readonly _keyColumnPixelOffsetCache: Float32Array | null;363private _cachedWidth: number = -1;364365constructor(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping) {366this.domNode = domNode;367this.input = renderLineInput;368const keyColumnCount = Math.floor(renderLineInput.lineContent.length / Constants.MaxMonospaceDistance);369if (keyColumnCount > 0) {370this._keyColumnPixelOffsetCache = new Float32Array(keyColumnCount);371for (let i = 0; i < keyColumnCount; i++) {372this._keyColumnPixelOffsetCache[i] = -1;373}374} else {375this._keyColumnPixelOffsetCache = null;376}377378this._characterMapping = characterMapping;379this._charWidth = renderLineInput.spaceWidth;380}381382public getWidth(context: DomReadingContext | null): number {383if (!this.domNode || this.input.lineContent.length < Constants.MaxMonospaceDistance) {384const horizontalOffset = this._characterMapping.getHorizontalOffset(this._characterMapping.length);385return Math.round(this._charWidth * horizontalOffset);386}387if (this._cachedWidth === -1) {388this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth;389context?.markDidDomLayout();390}391return this._cachedWidth;392}393394public getWidthIsFast(): boolean {395return (this.input.lineContent.length < Constants.MaxMonospaceDistance) || this._cachedWidth !== -1;396}397398public resetCachedWidth(): void {399this._cachedWidth = -1;400}401402public monospaceAssumptionsAreValid(): boolean {403if (!this.domNode) {404return monospaceAssumptionsAreValid;405}406if (this.input.lineContent.length < Constants.MaxMonospaceDistance) {407const expectedWidth = this.getWidth(null);408const actualWidth = (<HTMLSpanElement>this.domNode.domNode.firstChild).offsetWidth;409if (Math.abs(expectedWidth - actualWidth) >= 2) {410// more than 2px off411console.warn(`monospace assumptions have been violated, therefore disabling monospace optimizations!`);412monospaceAssumptionsAreValid = false;413}414}415return monospaceAssumptionsAreValid;416}417418public toSlowRenderedLine(): RenderedViewLine {419return createRenderedLine(this.domNode, this.input, this._characterMapping, ForeignElementType.None);420}421422public getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {423const startPosition = this._getColumnPixelOffset(lineNumber, startColumn, context);424const endPosition = this._getColumnPixelOffset(lineNumber, endColumn, context);425return [new FloatHorizontalRange(startPosition, endPosition - startPosition)];426}427428private _getColumnPixelOffset(lineNumber: number, column: number, context: DomReadingContext): number {429if (column <= Constants.MaxMonospaceDistance) {430const horizontalOffset = this._characterMapping.getHorizontalOffset(column);431return this._charWidth * horizontalOffset;432}433434const keyColumnOrdinal = Math.floor((column - 1) / Constants.MaxMonospaceDistance) - 1;435const keyColumn = (keyColumnOrdinal + 1) * Constants.MaxMonospaceDistance + 1;436let keyColumnPixelOffset = -1;437if (this._keyColumnPixelOffsetCache) {438keyColumnPixelOffset = this._keyColumnPixelOffsetCache[keyColumnOrdinal];439if (keyColumnPixelOffset === -1) {440keyColumnPixelOffset = this._actualReadPixelOffset(lineNumber, keyColumn, context);441this._keyColumnPixelOffsetCache[keyColumnOrdinal] = keyColumnPixelOffset;442}443}444445if (keyColumnPixelOffset === -1) {446// Could not read actual key column pixel offset447const horizontalOffset = this._characterMapping.getHorizontalOffset(column);448return this._charWidth * horizontalOffset;449}450451const keyColumnHorizontalOffset = this._characterMapping.getHorizontalOffset(keyColumn);452const horizontalOffset = this._characterMapping.getHorizontalOffset(column);453return keyColumnPixelOffset + this._charWidth * (horizontalOffset - keyColumnHorizontalOffset);454}455456private _getReadingTarget(myDomNode: FastDomNode<HTMLElement>): HTMLElement {457return <HTMLSpanElement>myDomNode.domNode.firstChild;458}459460private _actualReadPixelOffset(lineNumber: number, column: number, context: DomReadingContext): number {461if (!this.domNode) {462return -1;463}464const domPosition = this._characterMapping.getDomPosition(column);465const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(this.domNode), domPosition.partIndex, domPosition.charIndex, domPosition.partIndex, domPosition.charIndex, context);466if (!r || r.length === 0) {467return -1;468}469return r[0].left;470}471472public getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number {473return getColumnOfNodeOffset(this._characterMapping, spanNode, offset);474}475}476477/**478* Every time we render a line, we save what we have rendered in an instance of this class.479*/480class RenderedViewLine implements IRenderedViewLine {481482public domNode: FastDomNode<HTMLElement> | null;483public readonly input: RenderLineInput;484485protected readonly _characterMapping: CharacterMapping;486private readonly _isWhitespaceOnly: boolean;487private readonly _containsForeignElements: ForeignElementType;488private _cachedWidth: number;489490/**491* This is a map that is used only when the line is guaranteed to be rendered LTR and has no RTL text.492*/493private readonly _pixelOffsetCache: Float32Array | null;494495constructor(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType) {496this.domNode = domNode;497this.input = renderLineInput;498this._characterMapping = characterMapping;499this._isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent);500this._containsForeignElements = containsForeignElements;501this._cachedWidth = -1;502503this._pixelOffsetCache = null;504if (renderLineInput.isLTR) {505this._pixelOffsetCache = new Float32Array(Math.max(2, this._characterMapping.length + 1));506for (let column = 0, len = this._characterMapping.length; column <= len; column++) {507this._pixelOffsetCache[column] = -1;508}509}510}511512// --- Reading from the DOM methods513514protected _getReadingTarget(myDomNode: FastDomNode<HTMLElement>): HTMLElement {515return <HTMLSpanElement>myDomNode.domNode.firstChild;516}517518/**519* Width of the line in pixels520*/521public getWidth(context: DomReadingContext | null): number {522if (!this.domNode) {523return 0;524}525if (this._cachedWidth === -1) {526this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth;527context?.markDidDomLayout();528}529return this._cachedWidth;530}531532public getWidthIsFast(): boolean {533if (this._cachedWidth === -1) {534return false;535}536return true;537}538539public resetCachedWidth(): void {540this._cachedWidth = -1;541if (this._pixelOffsetCache !== null) {542for (let column = 0, len = this._pixelOffsetCache.length; column < len; column++) {543this._pixelOffsetCache[column] = -1;544}545}546}547548/**549* Visible ranges for a model range550*/551public getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {552if (!this.domNode) {553return null;554}555if (this._pixelOffsetCache !== null) {556// the text is guaranteed to be entirely LTR557const startOffset = this._readPixelOffset(this.domNode, lineNumber, startColumn, context);558if (startOffset === -1) {559return null;560}561562const endOffset = this._readPixelOffset(this.domNode, lineNumber, endColumn, context);563if (endOffset === -1) {564return null;565}566567return [new FloatHorizontalRange(startOffset, endOffset - startOffset)];568}569570return this._readVisibleRangesForRange(this.domNode, lineNumber, startColumn, endColumn, context);571}572573protected _readVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {574if (startColumn === endColumn) {575const pixelOffset = this._readPixelOffset(domNode, lineNumber, startColumn, context);576if (pixelOffset === -1) {577return null;578} else {579return [new FloatHorizontalRange(pixelOffset, 0)];580}581} else {582return this._readRawVisibleRangesForRange(domNode, startColumn, endColumn, context);583}584}585586protected _readPixelOffset(domNode: FastDomNode<HTMLElement>, lineNumber: number, column: number, context: DomReadingContext): number {587if (this.input.isLTR && this._characterMapping.length === 0) {588// This line has no content589if (this._containsForeignElements === ForeignElementType.None) {590// We can assume the line is really empty591return 0;592}593if (this._containsForeignElements === ForeignElementType.After) {594// We have foreign elements after the (empty) line595return 0;596}597if (this._containsForeignElements === ForeignElementType.Before) {598// We have foreign elements before the (empty) line599return this.getWidth(context);600}601// We have foreign elements before & after the (empty) line602const readingTarget = this._getReadingTarget(domNode);603if (readingTarget.firstChild) {604context.markDidDomLayout();605return (<HTMLSpanElement>readingTarget.firstChild).offsetWidth;606} else {607return 0;608}609}610611if (this._pixelOffsetCache !== null) {612// the text is guaranteed to be LTR613614const cachedPixelOffset = this._pixelOffsetCache[column];615if (cachedPixelOffset !== -1) {616return cachedPixelOffset;617}618619const result = this._actualReadPixelOffset(domNode, lineNumber, column, context);620this._pixelOffsetCache[column] = result;621return result;622}623624return this._actualReadPixelOffset(domNode, lineNumber, column, context);625}626627private _actualReadPixelOffset(domNode: FastDomNode<HTMLElement>, lineNumber: number, column: number, context: DomReadingContext): number {628if (this._characterMapping.length === 0) {629// This line has no content630const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), 0, 0, 0, 0, context);631if (!r || r.length === 0) {632return -1;633}634return r[0].left;635}636637if (this.input.isLTR && column === this._characterMapping.length && this._isWhitespaceOnly && this._containsForeignElements === ForeignElementType.None) {638// This branch helps in the case of whitespace only lines which have a width set639return this.getWidth(context);640}641642const domPosition = this._characterMapping.getDomPosition(column);643644const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), domPosition.partIndex, domPosition.charIndex, domPosition.partIndex, domPosition.charIndex, context);645if (!r || r.length === 0) {646return -1;647}648const result = r[0].left;649if (this.input.isBasicASCII) {650const horizontalOffset = this._characterMapping.getHorizontalOffset(column);651const expectedResult = Math.round(this.input.spaceWidth * horizontalOffset);652if (Math.abs(expectedResult - result) <= 1) {653return expectedResult;654}655}656return result;657}658659private _readRawVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {660661if (this.input.isLTR && startColumn === 1 && endColumn === this._characterMapping.length) {662// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line663664return [new FloatHorizontalRange(0, this.getWidth(context))];665}666667const startDomPosition = this._characterMapping.getDomPosition(startColumn);668const endDomPosition = this._characterMapping.getDomPosition(endColumn);669670return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startDomPosition.partIndex, startDomPosition.charIndex, endDomPosition.partIndex, endDomPosition.charIndex, context);671}672673/**674* Returns the column for the text found at a specific offset inside a rendered dom node675*/676public getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number {677return getColumnOfNodeOffset(this._characterMapping, spanNode, offset);678}679}680681class WebKitRenderedViewLine extends RenderedViewLine {682protected override _readVisibleRangesForRange(domNode: FastDomNode<HTMLElement>, lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null {683const output = super._readVisibleRangesForRange(domNode, lineNumber, startColumn, endColumn, context);684685if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) {686return output;687}688689// WebKit is buggy and returns an expanded range (to contain words in some cases)690// The last client rect is enlarged (I think)691if (this.input.isLTR) {692// This is an attempt to patch things up693// Find position of last column694const endPixelOffset = this._readPixelOffset(domNode, lineNumber, endColumn, context);695if (endPixelOffset !== -1) {696const lastRange = output[output.length - 1];697if (lastRange.left < endPixelOffset) {698// Trim down the width of the last visible range to not go after the last column's position699lastRange.width = endPixelOffset - lastRange.left;700}701}702}703704return output;705}706}707708const createRenderedLine: (domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType) => RenderedViewLine = (function () {709if (browser.isWebKit) {710return createWebKitRenderedLine;711}712return createNormalRenderedLine;713})();714715function createWebKitRenderedLine(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType): RenderedViewLine {716return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsForeignElements);717}718719function createNormalRenderedLine(domNode: FastDomNode<HTMLElement> | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsForeignElements: ForeignElementType): RenderedViewLine {720return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsForeignElements);721}722723export function getColumnOfNodeOffset(characterMapping: CharacterMapping, spanNode: HTMLElement, offset: number): number {724const spanNodeTextContentLength = spanNode.textContent.length;725726let spanIndex = -1;727while (spanNode) {728spanNode = <HTMLElement>spanNode.previousSibling;729spanIndex++;730}731732return characterMapping.getColumn(new DomPosition(spanIndex, offset), spanNodeTextContentLength);733}734735736