diff --git a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts new file mode 100644 index 00000000000..24a7d714ef3 --- /dev/null +++ b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {HorizontalRange} from 'vs/editor/common/editorCommon'; + +export class RangeUtil { + + /** + * Reusing the same range here + * because IE is buggy and constantly freezes when using a large number + * of ranges and calling .detach on them + */ + private static _handyReadyRange:Range; + + private static _createRange(): Range { + if (!this._handyReadyRange) { + this._handyReadyRange = document.createRange(); + } + return this._handyReadyRange; + } + + private static _detachRange(range:Range, endNode:HTMLElement): void { + // Move range out of the span node, IE doesn't like having many ranges in + // the same spot and will act badly for lines containing dashes ('-') + range.selectNodeContents(endNode); + } + + private static _readClientRects(startElement:Node, startOffset:number, endElement:Node, endOffset:number, endNode:HTMLElement): ClientRectList { + let range = this._createRange(); + try { + range.setStart(startElement, startOffset); + range.setEnd(endElement, endOffset); + + return range.getClientRects(); + } catch (e) { + // This is life ... + return null; + } finally { + this._detachRange(range, endNode); + } + } + + private static _createHorizontalRangesFromClientRects(clientRects:ClientRectList, clientRectDeltaLeft:number, scaleRatio:number): HorizontalRange[] { + if (!clientRects || clientRects.length === 0) { + return null; + } + + let result:HorizontalRange[] = []; + let prevLeft = Math.max(0, clientRects[0].left * scaleRatio - clientRectDeltaLeft); + let prevWidth = clientRects[0].width * scaleRatio; + + for (let i = 1, len = clientRects.length; i < len; i++) { + let myLeft = Math.max(0, clientRects[i].left * scaleRatio - clientRectDeltaLeft); + let myWidth = clientRects[i].width * scaleRatio; + + if (myLeft < prevLeft) { + console.error('Unexpected: RangeUtil._createHorizontalRangesFromClientRects: client rects are not sorted'); + } + + if (prevLeft + prevWidth + 0.9 /* account for browser's rounding errors*/ >= myLeft) { + prevWidth = Math.max(prevWidth, myLeft + myWidth - prevLeft); + } else { + result.push(new HorizontalRange(prevLeft, prevWidth)); + prevLeft = myLeft; + prevWidth = myWidth; + } + } + + result.push(new HorizontalRange(prevLeft, prevWidth)); + + return result; + } + + public static readHorizontalRanges(domNode:HTMLElement, startChildIndex:number, startOffset:number, endChildIndex:number, endOffset:number, clientRectDeltaLeft:number, scaleRatio:number, endNode:HTMLElement): HorizontalRange[] { + // Panic check + let min = 0; + let max = domNode.children.length - 1; + if (min > max) { + return null; + } + startChildIndex = Math.min(max, Math.max(min, startChildIndex)); + endChildIndex = Math.min(max, Math.max(min, endChildIndex)); + + // If crossing over to a span only to select offset 0, then use the previous span's maximum offset + // Chrome is buggy and doesn't handle 0 offsets well sometimes. + if (startChildIndex !== endChildIndex) { + if (endChildIndex > 0 && endOffset === 0) { + endChildIndex--; + endOffset = Number.MAX_VALUE; + } + } + + let startElement = domNode.children[startChildIndex].firstChild; + let endElement = domNode.children[endChildIndex].firstChild; + + if (!startElement || !endElement) { + return null; + } + + startOffset = Math.min(startElement.textContent.length, Math.max(0, startOffset)); + endOffset = Math.min(endElement.textContent.length, Math.max(0, endOffset)); + + let clientRects = this._readClientRects(startElement, startOffset, endElement, endOffset, endNode); + return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft, scaleRatio); + } +} diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 2272bfc2eac..f88bbcd71c0 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -11,6 +11,7 @@ import {ILineParts, createLineParts} from 'vs/editor/common/viewLayout/viewLineP import {renderLine, RenderLineInput} from 'vs/editor/common/viewLayout/viewLineRenderer'; import {ClassNames, IViewContext} from 'vs/editor/browser/editorBrowser'; import {IVisibleLineData} from 'vs/editor/browser/view/viewLayer'; +import {RangeUtil} from 'vs/editor/browser/viewParts/lines/rangeUtil'; export class ViewLine implements IVisibleLineData { @@ -412,107 +413,6 @@ class WebKitViewLine extends ViewLine { } } -class RangeUtil { - - /** - * Reusing the same range here - * because IE is buggy and constantly freezes when using a large number - * of ranges and calling .detach on them - */ - private static _handyReadyRange:Range; - - private static _createRange(): Range { - if (!this._handyReadyRange) { - this._handyReadyRange = document.createRange(); - } - return this._handyReadyRange; - } - - private static _detachRange(range:Range, endNode:HTMLElement): void { - // Move range out of the span node, IE doesn't like having many ranges in - // the same spot and will act badly for lines containing dashes ('-') - range.selectNodeContents(endNode); - } - - private static _readClientRects(startElement:Node, startOffset:number, endElement:Node, endOffset:number, endNode:HTMLElement): ClientRectList { - let range = this._createRange(); - try { - range.setStart(startElement, startOffset); - range.setEnd(endElement, endOffset); - - return range.getClientRects(); - } catch (e) { - // This is life ... - return null; - } finally { - this._detachRange(range, endNode); - } - } - - private static _createHorizontalRangesFromClientRects(clientRects:ClientRectList, clientRectDeltaLeft:number, scaleRatio:number): HorizontalRange[] { - if (!clientRects || clientRects.length === 0) { - return null; - } - - let result:HorizontalRange[] = []; - let prevLeft = Math.max(0, clientRects[0].left * scaleRatio - clientRectDeltaLeft); - let prevWidth = clientRects[0].width * scaleRatio; - - for (let i = 1, len = clientRects.length; i < len; i++) { - let myLeft = Math.max(0, clientRects[i].left * scaleRatio - clientRectDeltaLeft); - let myWidth = clientRects[i].width * scaleRatio; - - if (myLeft < prevLeft) { - console.error('Unexpected: RangeUtil._createHorizontalRangesFromClientRects: client rects are not sorted'); - } - - if (prevLeft + prevWidth + 0.9 /* account for browser's rounding errors*/ >= myLeft) { - prevWidth = Math.max(prevWidth, myLeft + myWidth - prevLeft); - } else { - result.push(new HorizontalRange(prevLeft, prevWidth)); - prevLeft = myLeft; - prevWidth = myWidth; - } - } - - result.push(new HorizontalRange(prevLeft, prevWidth)); - - return result; - } - - public static readHorizontalRanges(domNode:HTMLElement, startChildIndex:number, startOffset:number, endChildIndex:number, endOffset:number, clientRectDeltaLeft:number, scaleRatio:number, endNode:HTMLElement): HorizontalRange[] { - // Panic check - let min = 0; - let max = domNode.children.length - 1; - if (min > max) { - return null; - } - startChildIndex = Math.min(max, Math.max(min, startChildIndex)); - endChildIndex = Math.min(max, Math.max(min, endChildIndex)); - - // If crossing over to a span only to select offset 0, then use the previous span's maximum offset - // Chrome is buggy and doesn't handle 0 offsets well sometimes. - if (startChildIndex !== endChildIndex) { - if (endChildIndex > 0 && endOffset === 0) { - endChildIndex--; - endOffset = Number.MAX_VALUE; - } - } - - let startElement = domNode.children[startChildIndex].firstChild; - let endElement = domNode.children[endChildIndex].firstChild; - - if (!startElement || !endElement) { - return null; - } - - startOffset = Math.min(startElement.textContent.length, Math.max(0, startOffset)); - endOffset = Math.min(endElement.textContent.length, Math.max(0, endOffset)); - - let clientRects = this._readClientRects(startElement, startOffset, endElement, endOffset, endNode); - return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft, scaleRatio); - } -} function findIndexInArrayWithMax(lineParts:ILineParts, desiredIndex: number, maxResult:number): number { let r = lineParts.findIndexOfOffset(desiredIndex);