/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as browser from '../../../../../vs/base/browser/browser'; import { FastDomNode, createFastDomNode, } from '../../../../../vs/base/browser/fastDomNode'; import * as platform from '../../../../../vs/base/common/platform'; import { IVisibleLine } from '../../../../../vs/editor/browser/view/viewLayer'; import { RangeUtil } from '../../../../../vs/editor/browser/viewParts/lines/rangeUtil'; import { IStringBuilder } from '../../../../../vs/editor/common/core/stringBuilder'; import { IConfiguration } from '../../../../../vs/editor/common/editorCommon'; import { HorizontalRange, VisibleRanges, } from '../../../../../vs/editor/common/view/renderingContext'; import { LineDecoration } from '../../../../../vs/editor/common/viewLayout/lineDecorations'; import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, LineRange, } from '../../../../../vs/editor/common/viewLayout/viewLineRenderer'; import { ViewportData } from '../../../../../vs/editor/common/viewLayout/viewLinesViewportData'; import { InlineDecorationType } from '../../../../../vs/editor/common/viewModel/viewModel'; import { ColorScheme } from '../../../../../vs/platform/theme/common/theme'; import { EditorOption, EditorFontLigatures, } from '../../../../../vs/editor/common/config/editorOptions'; const canUseFastRenderedViewLine = (function () { if (platform.isNative) { // In VSCode we know very well when the zoom level changes return true; } if (platform.isLinux || browser.isFirefox || browser.isSafari) { // On Linux, it appears that zooming affects char widths (in pixels), which is unexpected. // -- // Even though we read character widths correctly, having read them at a specific zoom level // does not mean they are the same at the current zoom level. // -- // This could be improved if we ever figure out how to get an event when browsers zoom, // but until then we have to stick with reading client rects. // -- // The same has been observed with Firefox on Windows7 // -- // The same has been oversved with Safari return false; } return true; })(); let monospaceAssumptionsAreValid = true; export class DomReadingContext { private readonly _domNode: HTMLElement; private _clientRectDeltaLeft: number; private _clientRectDeltaLeftRead: boolean; readonly _writingMode: string = ''; public get clientRectDeltaLeft(): number { if (!this._clientRectDeltaLeftRead) { this._clientRectDeltaLeftRead = true; if (this._writingMode === 'left-to-right-horizontal-writing') { this._clientRectDeltaLeft = this._domNode.getBoundingClientRect().left; } if (this._writingMode === 'right-to-left-vertical-writing') { this._clientRectDeltaLeft = this._domNode.getBoundingClientRect().top; } } return this._clientRectDeltaLeft; } public readonly endNode: HTMLElement; constructor(domNode: HTMLElement, endNode: HTMLElement) { this._domNode = domNode; // console.log(`${domNode.dataset['writingMode']}`); this._writingMode = domNode.dataset['writingMode']!; this._clientRectDeltaLeft = 0; this._clientRectDeltaLeftRead = false; this.endNode = endNode; } } // export class DomReadingContext { // private readonly _domNode: HTMLElement; // private _clientRectDeltaLeft: number; // private _clientRectDeltaLeftRead: boolean; // private readonly _writingMode: string = ''; // public get clientRectDeltaLeft(): number { // if (!this._clientRectDeltaLeftRead) { // this._clientRectDeltaLeftRead = true; // const clientRect = this._domNode.getBoundingClientRect(); // if (this._writingMode === 'right-to-left-horizontal-writing') { // this._clientRectDeltaLeft = clientRect.top; // } else { // this._clientRectDeltaLeft = clientRect.left; // } // } // return this._clientRectDeltaLeft; // } // public readonly endNode: HTMLElement; // constructor(domNode: HTMLElement, endNode: HTMLElement) { // this._domNode = domNode; // // console.log(`${domNode.dataset['writingMode']}`); // this._writingMode = domNode.dataset['writingMode']!; // this._clientRectDeltaLeft = 0; // this._clientRectDeltaLeftRead = false; // this.endNode = endNode; // } // } export class ViewLineOptions { public readonly themeType: ColorScheme; public readonly renderWhitespace: | 'none' | 'boundary' | 'selection' | 'trailing' | 'all'; public readonly renderControlCharacters: boolean; public readonly spaceWidth: number; public readonly middotWidth: number; public readonly wsmiddotWidth: number; public readonly useMonospaceOptimizations: boolean; public readonly canUseHalfwidthRightwardsArrow: boolean; public readonly lineHeight: number; public readonly stopRenderingLineAfter: number; public readonly fontLigatures: string; constructor(config: IConfiguration, themeType: ColorScheme) { this.themeType = themeType; const options = config.options; const fontInfo = options.get(EditorOption.fontInfo); this.renderWhitespace = options.get(EditorOption.renderWhitespace); this.renderControlCharacters = options.get( EditorOption.renderControlCharacters ); this.spaceWidth = fontInfo.spaceWidth; this.middotWidth = fontInfo.middotWidth; this.wsmiddotWidth = fontInfo.wsmiddotWidth; this.useMonospaceOptimizations = fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations); this.canUseHalfwidthRightwardsArrow = fontInfo.canUseHalfwidthRightwardsArrow; this.lineHeight = options.get(EditorOption.lineHeight); this.stopRenderingLineAfter = options.get( EditorOption.stopRenderingLineAfter ); this.fontLigatures = options.get(EditorOption.fontLigatures); } public equals(other: ViewLineOptions): boolean { return ( this.themeType === other.themeType && this.renderWhitespace === other.renderWhitespace && this.renderControlCharacters === other.renderControlCharacters && this.spaceWidth === other.spaceWidth && this.middotWidth === other.middotWidth && this.wsmiddotWidth === other.wsmiddotWidth && this.useMonospaceOptimizations === other.useMonospaceOptimizations && this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow && this.lineHeight === other.lineHeight && this.stopRenderingLineAfter === other.stopRenderingLineAfter && this.fontLigatures === other.fontLigatures ); } } export class ViewLine implements IVisibleLine { public _viewLineNumber: number | undefined = undefined; // 追加したコード public static readonly CLASS_NAME = 'view-line'; private _options: ViewLineOptions; private _isMaybeInvalid: boolean; private _renderedViewLine: IRenderedViewLine | null; constructor(options: ViewLineOptions, private readonly _editorId = '') { this._options = options; this._isMaybeInvalid = true; this._renderedViewLine = null; } public setViewLineNumber(viewLineNumber: number): void { this._viewLineNumber = viewLineNumber; // this._renderedViewLine._viewLineNumber = viewLineNumber; } // --- begin IVisibleLineData public getDomNode(): HTMLElement | null { if (this._renderedViewLine && this._renderedViewLine.domNode) { return this._renderedViewLine.domNode.domNode; } return null; } public setDomNode(domNode: HTMLElement): void { if (this._renderedViewLine) { this._renderedViewLine.domNode = createFastDomNode(domNode); } else { throw new Error('I have no rendered view line to set the dom node to...'); } } public onContentChanged(): void { this._isMaybeInvalid = true; } public onTokensChanged(): void { this._isMaybeInvalid = true; } public onDecorationsChanged(): void { this._isMaybeInvalid = true; } public onOptionsChanged(newOptions: ViewLineOptions): void { this._isMaybeInvalid = true; this._options = newOptions; } public onSelectionChanged(): boolean { if ( this._options.themeType === ColorScheme.HIGH_CONTRAST || this._options.renderWhitespace === 'selection' ) { this._isMaybeInvalid = true; return true; } return false; } public renderLine( lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: IStringBuilder ): boolean { if (this._isMaybeInvalid === false) { // it appears that nothing relevant has changed return false; } this._isMaybeInvalid = false; const lineData = viewportData.getViewLineRenderingData(lineNumber); const options = this._options; const actualInlineDecorations = LineDecoration.filter( lineData.inlineDecorations, lineNumber, lineData.minColumn, lineData.maxColumn ); // Only send selection information when needed for rendering whitespace let selectionsOnLine: LineRange[] | null = null; if ( options.themeType === ColorScheme.HIGH_CONTRAST || this._options.renderWhitespace === 'selection' ) { const selections = viewportData.selections; for (const selection of selections) { if ( selection.endLineNumber < lineNumber || selection.startLineNumber > lineNumber ) { // Selection does not intersect line continue; } const startColumn = selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn; const endColumn = selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn; if (startColumn < endColumn) { if ( options.themeType === ColorScheme.HIGH_CONTRAST || this._options.renderWhitespace !== 'selection' ) { actualInlineDecorations.push( new LineDecoration( startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular ) ); } else { if (!selectionsOnLine) { selectionsOnLine = []; } selectionsOnLine.push( new LineRange(startColumn - 1, endColumn - 1) ); } } } } const renderLineInput = new RenderLineInput( options.useMonospaceOptimizations, options.canUseHalfwidthRightwardsArrow, lineData.content, lineData.continuesWithWrappedLine, lineData.isBasicASCII, lineData.containsRTL, lineData.minColumn - 1, lineData.tokens, actualInlineDecorations, lineData.tabSize, lineData.startVisibleColumn, options.spaceWidth, options.middotWidth, options.wsmiddotWidth, options.stopRenderingLineAfter, options.renderWhitespace, options.renderControlCharacters, options.fontLigatures !== EditorFontLigatures.OFF, selectionsOnLine ); if ( this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput) ) { // no need to do anything, we have the same render input return false; } sb.appendASCIIString('
'); const output = renderViewLine(renderLineInput, sb); sb.appendASCIIString('
'); let renderedViewLine: IRenderedViewLine | null = null; if ( monospaceAssumptionsAreValid && canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None ) { if ( lineData.content.length < 300 && renderLineInput.lineTokens.getCount() < 100 ) { // Browser rounding errors have been observed in Chrome and IE, so using the fast // view line only for short lines. Please test before removing the length check... // --- // Another rounding error has been observed on Linux in VSCode, where width // rounding errors add up to an observable large number... // --- // Also see another example of rounding errors on Windows in // https://github.com/microsoft/vscode/issues/33178 renderedViewLine = new FastRenderedViewLine( this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, output.characterMapping, this._editorId, this._viewLineNumber! ); } } if (!renderedViewLine) { renderedViewLine = createRenderedLine( this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, output.characterMapping, output.containsRTL, output.containsForeignElements, this._editorId, this._viewLineNumber! ); } this._renderedViewLine = renderedViewLine; return true; } public layoutLine(lineNumber: number, deltaTop: number): void { if (this._renderedViewLine && this._renderedViewLine.domNode) { this._renderedViewLine.domNode.setTop(deltaTop); this._renderedViewLine.domNode.setHeight(this._options.lineHeight); } } // --- end IVisibleLineData public getWidth(): number { if (!this._renderedViewLine) { return 0; } this._renderedViewLine.setViewLineNumber(this._viewLineNumber!); return this._renderedViewLine.getWidth(); } public getWidthIsFast(): boolean { if (!this._renderedViewLine) { return true; } return this._renderedViewLine.getWidthIsFast(); } public needsMonospaceFontCheck(): boolean { if (!this._renderedViewLine) { return false; } return this._renderedViewLine instanceof FastRenderedViewLine; } public monospaceAssumptionsAreValid(): boolean { if (!this._renderedViewLine) { return monospaceAssumptionsAreValid; } if (this._renderedViewLine instanceof FastRenderedViewLine) { return this._renderedViewLine.monospaceAssumptionsAreValid(); } return monospaceAssumptionsAreValid; } public onMonospaceAssumptionsInvalidated(): void { if ( this._renderedViewLine && this._renderedViewLine instanceof FastRenderedViewLine ) { this._renderedViewLine = this._renderedViewLine.toSlowRenderedLine(); } } public getVisibleRangesForRange( startColumn: number, endColumn: number, context: DomReadingContext ): VisibleRanges | null { if (!this._renderedViewLine) { return null; } startColumn = startColumn | 0; // @perf endColumn = endColumn | 0; // @perf startColumn = Math.min( this._renderedViewLine.input.lineContent.length + 1, Math.max(1, startColumn) ); endColumn = Math.min( this._renderedViewLine.input.lineContent.length + 1, Math.max(1, endColumn) ); const stopRenderingLineAfter = this._renderedViewLine.input.stopRenderingLineAfter | 0; // @perf let outsideRenderedLine = false; if ( stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1 && endColumn > stopRenderingLineAfter + 1 ) { // This range is obviously not visible outsideRenderedLine = true; } if ( stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1 ) { startColumn = stopRenderingLineAfter + 1; } if ( stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter + 1 ) { endColumn = stopRenderingLineAfter + 1; } const horizontalRanges = this._renderedViewLine.getVisibleRangesForRange( startColumn, endColumn, context ); if (horizontalRanges && horizontalRanges.length > 0) { return new VisibleRanges(outsideRenderedLine, horizontalRanges); } return null; } public getColumnOfNodeOffset( lineNumber: number, spanNode: HTMLElement, offset: number ): number { if (!this._renderedViewLine) { return 1; } return this._renderedViewLine.getColumnOfNodeOffset( lineNumber, spanNode, offset ); } } interface IRenderedViewLine { domNode: FastDomNode | null; readonly input: RenderLineInput; getWidth(): number; getWidthIsFast(): boolean; getVisibleRangesForRange( startColumn: number, endColumn: number, context: DomReadingContext ): HorizontalRange[] | null; getColumnOfNodeOffset( lineNumber: number, spanNode: HTMLElement, offset: number ): number; setViewLineNumber(viewLineNumber: number): void; } /** * A rendered line which is guaranteed to contain only regular ASCII and is rendered with a monospace font. */ class FastRenderedViewLine implements IRenderedViewLine { public domNode: FastDomNode | null; public readonly input: RenderLineInput; private readonly _characterMapping: CharacterMapping; private readonly _charWidth: number; constructor( domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, private readonly _editorId: string, private _viewLineNumber: number ) { this.domNode = domNode; this.input = renderLineInput; this._characterMapping = characterMapping; this._charWidth = renderLineInput.spaceWidth; } public setViewLineNumber(viewLineNumber: number): void { this._viewLineNumber = viewLineNumber; } public getWidth(): number { return this._getCharPosition(this._characterMapping.length); } public getWidthIsFast(): boolean { return true; } public monospaceAssumptionsAreValid(): boolean { if (!this.domNode) { return monospaceAssumptionsAreValid; } const expectedWidth = this.getWidth(); const actualWidth = (this.domNode.domNode.firstChild) .offsetWidth; if (Math.abs(expectedWidth - actualWidth) >= 2) { // more than 2px off console.warn( `monospace assumptions have been violated, therefore disabling monospace optimizations!` ); monospaceAssumptionsAreValid = false; } return monospaceAssumptionsAreValid; } public toSlowRenderedLine(): RenderedViewLine { return createRenderedLine( this.domNode, this.input, this._characterMapping, false, ForeignElementType.None, this._editorId, this._viewLineNumber ); } public getVisibleRangesForRange( startColumn: number, endColumn: number, context: DomReadingContext ): HorizontalRange[] | null { const startPosition = this._getCharPosition(startColumn); const endPosition = this._getCharPosition(endColumn); return [new HorizontalRange(startPosition, endPosition - startPosition)]; } private _getCharPosition(column: number): number { const charOffset = this._characterMapping.getAbsoluteOffsets(); if (charOffset.length === 0) { // No characters on this line return 0; } return Math.round(this._charWidth * charOffset[column - 1]); } public getColumnOfNodeOffset( lineNumber: number, spanNode: HTMLElement, offset: number ): number { const spanNodeTextContentLength = spanNode.textContent!.length; let spanIndex = -1; while (spanNode) { spanNode = spanNode.previousSibling; spanIndex++; } const charOffset = this._characterMapping.partDataToCharOffset( spanIndex, spanNodeTextContentLength, offset ); return charOffset + 1; } } /** * Every time we render a line, we save what we have rendered in an instance of this class. */ class RenderedViewLine implements IRenderedViewLine { public domNode: FastDomNode | null; public readonly input: RenderLineInput; protected readonly _characterMapping: CharacterMapping; private readonly _isWhitespaceOnly: boolean; private readonly _containsForeignElements: ForeignElementType; private _cachedWidth: number; /** * This is a map that is used only when the line is guaranteed to have no RTL text. */ private readonly _pixelOffsetCache: Int32Array | null; constructor( domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType, private readonly _editorId = '', private _viewLineNumber: number ) { this.domNode = domNode; this.input = renderLineInput; this._characterMapping = characterMapping; this._isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent); this._containsForeignElements = containsForeignElements; this._cachedWidth = -1; this._pixelOffsetCache = null; if ( !containsRTL || this._characterMapping.length === 0 /* the line is empty */ ) { this._pixelOffsetCache = new Int32Array( Math.max(2, this._characterMapping.length + 1) ); for ( let column = 0, len = this._characterMapping.length; column <= len; column++ ) { this._pixelOffsetCache[column] = -1; } } } public setViewLineNumber(viewLineNumber: number): void { this._viewLineNumber = viewLineNumber; } // --- Reading from the DOM methods protected _getReadingTarget( myDomNode: FastDomNode ): HTMLElement { return myDomNode.domNode.firstChild; } /** * Width of the line in pixels */ public getWidth(): number { if (!this.domNode) { return 0; } if (this._cachedWidth === -1) { // this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth; const div = document.getElementById( `${this._editorId}-view-line-number:${this._viewLineNumber}` ) as HTMLDivElement; const writingMode = div.dataset['writingMode']; if (writingMode === 'right-to-left-vertical-writing') { const span = document.getElementById( `${this._editorId}-view-line-number:${this._viewLineNumber}` )?.firstChild as HTMLSpanElement; this._cachedWidth = span.offsetHeight; return this._cachedWidth; } const span = document.getElementById( `${this._editorId}-view-line-number:${this._viewLineNumber}` )?.firstChild as HTMLSpanElement; this._cachedWidth = span.offsetWidth; } return this._cachedWidth; } public getWidthIsFast(): boolean { if (this._cachedWidth === -1) { return false; } return true; } /** * Visible ranges for a model range */ public getVisibleRangesForRange( startColumn: number, endColumn: number, context: DomReadingContext ): HorizontalRange[] | null { if (!this.domNode) { return null; } if (this._pixelOffsetCache !== null) { // the text is LTR const startOffset = this._readPixelOffset( this.domNode, startColumn, context ); if (startOffset === -1) { return null; } const endOffset = this._readPixelOffset(this.domNode, endColumn, context); if (endOffset === -1) { return null; } return [new HorizontalRange(startOffset, endOffset - startOffset)]; } return this._readVisibleRangesForRange( this.domNode, startColumn, endColumn, context ); } protected _readVisibleRangesForRange( domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext ): HorizontalRange[] | null { if (startColumn === endColumn) { const pixelOffset = this._readPixelOffset(domNode, startColumn, context); if (pixelOffset === -1) { return null; } else { return [new HorizontalRange(pixelOffset, 0)]; } } else { return this._readRawVisibleRangesForRange( domNode, startColumn, endColumn, context ); } } protected _readPixelOffset( domNode: FastDomNode, column: number, context: DomReadingContext ): number { if (this._characterMapping.length === 0) { // This line has no content if (this._containsForeignElements === ForeignElementType.None) { // We can assume the line is really empty return 0; } if (this._containsForeignElements === ForeignElementType.After) { // We have foreign elements after the (empty) line return 0; } if (this._containsForeignElements === ForeignElementType.Before) { // We have foreign elements before the (empty) line return this.getWidth(); } // We have foreign elements before & after the (empty) line const readingTarget = this._getReadingTarget(domNode); if (readingTarget.firstChild) { return (readingTarget.firstChild).offsetWidth; } else { return 0; } } if (this._pixelOffsetCache !== null) { // the text is LTR const cachedPixelOffset = this._pixelOffsetCache[column]; if (cachedPixelOffset !== -1) { return cachedPixelOffset; } const result = this._actualReadPixelOffset(domNode, column, context); this._pixelOffsetCache[column] = result; return result; } return this._actualReadPixelOffset(domNode, column, context); } private _actualReadPixelOffset( domNode: FastDomNode, column: number, context: DomReadingContext ): number { if (this._characterMapping.length === 0) { // This line has no content const r = RangeUtil.readHorizontalRanges( this._getReadingTarget(domNode), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode ); if (!r || r.length === 0) { return -1; } return r[0].left; } if ( column === this._characterMapping.length && this._isWhitespaceOnly && this._containsForeignElements === ForeignElementType.None ) { // This branch helps in the case of whitespace only lines which have a width set return this.getWidth(); } const partData = this._characterMapping.charOffsetToPartData(column - 1); const partIndex = CharacterMapping.getPartIndex(partData); const charOffsetInPart = CharacterMapping.getCharIndex(partData); const span = document.getElementById( `${this._editorId}-view-line-number:${this._viewLineNumber}` ); if (!span) { return -1; } const firstChild = span.firstChild as HTMLSpanElement; const r = RangeUtil.readHorizontalRanges( // this._getReadingTarget(domNode), firstChild, partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode, context._writingMode ); if (!r || r.length === 0) { return -1; } const result = r[0].left; if (this.input.isBasicASCII) { const charOffset = this._characterMapping.getAbsoluteOffsets(); const expectedResult = Math.round( this.input.spaceWidth * charOffset[column - 1] ); if (Math.abs(expectedResult - result) <= 1) { return expectedResult; } } return result; } private _readRawVisibleRangesForRange( domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext ): HorizontalRange[] | null { if (startColumn === 1 && endColumn === this._characterMapping.length) { // This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line return [new HorizontalRange(0, this.getWidth())]; } const startPartData = this._characterMapping.charOffsetToPartData( startColumn - 1 ); const startPartIndex = CharacterMapping.getPartIndex(startPartData); const startCharOffsetInPart = CharacterMapping.getCharIndex(startPartData); const endPartData = this._characterMapping.charOffsetToPartData( endColumn - 1 ); const endPartIndex = CharacterMapping.getPartIndex(endPartData); const endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData); return RangeUtil.readHorizontalRanges( this._getReadingTarget(domNode), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode ); } /** * Returns the column for the text found at a specific offset inside a rendered dom node */ public getColumnOfNodeOffset( lineNumber: number, spanNode: HTMLElement, offset: number ): number { const spanNodeTextContentLength = spanNode.textContent!.length; let spanIndex = -1; while (spanNode) { spanNode = spanNode.previousSibling; spanIndex++; } const charOffset = this._characterMapping.partDataToCharOffset( spanIndex, spanNodeTextContentLength, offset ); return charOffset + 1; } } class WebKitRenderedViewLine extends RenderedViewLine { protected override _readVisibleRangesForRange( domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext ): HorizontalRange[] | null { const output = super._readVisibleRangesForRange( domNode, startColumn, endColumn, context ); if ( !output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length) ) { return output; } // WebKit is buggy and returns an expanded range (to contain words in some cases) // The last client rect is enlarged (I think) if (!this.input.containsRTL) { // This is an attempt to patch things up // Find position of last column const endPixelOffset = this._readPixelOffset(domNode, endColumn, context); if (endPixelOffset !== -1) { const lastRange = output[output.length - 1]; if (lastRange.left < endPixelOffset) { // Trim down the width of the last visible range to not go after the last column's position lastRange.width = endPixelOffset - lastRange.left; } } } return output; } } const createRenderedLine: ( domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType, editorId: string, viewLineNumber: number ) => RenderedViewLine = (function () { if (browser.isWebKit) { return createWebKitRenderedLine; } return createNormalRenderedLine; })(); function createWebKitRenderedLine( domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType, editorId: string, viewLineNumber: number ): RenderedViewLine { return new WebKitRenderedViewLine( domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements, editorId, viewLineNumber ); } function createNormalRenderedLine( domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType, editorId: string, viewLineNumber: number ): RenderedViewLine { return new RenderedViewLine( domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements, editorId, viewLineNumber ); }