viewLine.ts 18.0 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

A
Alex Dima 已提交
7
import * as browser from 'vs/base/browser/browser';
A
Alex Dima 已提交
8
import {FastDomNode, createFastDomNode} from 'vs/base/browser/styleMutator';
A
Alex Dima 已提交
9
import {HorizontalRange, IConfigurationChangedEvent, IModelDecoration} from 'vs/editor/common/editorCommon';
E
Erich Gamma 已提交
10
import {ILineParts, createLineParts} from 'vs/editor/common/viewLayout/viewLineParts';
A
Alex Dima 已提交
11
import {renderLine, RenderLineInput} from 'vs/editor/common/viewLayout/viewLineRenderer';
A
Alex Dima 已提交
12 13
import {ClassNames, IViewContext} from 'vs/editor/browser/editorBrowser';
import {IVisibleLineData} from 'vs/editor/browser/view/viewLayer';
E
Erich Gamma 已提交
14

A
Alex Dima 已提交
15
export class ViewLine implements IVisibleLineData {
E
Erich Gamma 已提交
16

A
Alex Dima 已提交
17
	protected _context:IViewContext;
A
Alex Dima 已提交
18
	private _renderWhitespace: boolean;
19
	private _spaceWidth: number;
A
Alex Dima 已提交
20 21 22 23
	private _lineHeight: number;
	private _stopRenderingLineAfter: number;
	protected _fontLigatures: boolean;

A
Alex Dima 已提交
24
	private _domNode: FastDomNode;
E
Erich Gamma 已提交
25 26 27 28 29 30

	private _lineParts: ILineParts;

	private _isInvalid: boolean;
	private _isMaybeInvalid: boolean;

A
Alex Dima 已提交
31
	protected _charOffsetInPart:number[];
E
Erich Gamma 已提交
32 33 34
	private _lastRenderedPartIndex:number;
	private _cachedWidth: number;

A
Alex Dima 已提交
35
	constructor(context:IViewContext) {
E
Erich Gamma 已提交
36
		this._context = context;
A
Alex Dima 已提交
37
		this._renderWhitespace = this._context.configuration.editor.renderWhitespace;
38
		this._spaceWidth = this._context.configuration.editor.spaceWidth;
A
Alex Dima 已提交
39 40 41 42
		this._lineHeight = this._context.configuration.editor.lineHeight;
		this._stopRenderingLineAfter = this._context.configuration.editor.stopRenderingLineAfter;
		this._fontLigatures = this._context.configuration.editor.fontLigatures;

E
Erich Gamma 已提交
43 44 45 46 47 48 49 50
		this._domNode = null;
		this._isInvalid = true;
		this._isMaybeInvalid = false;
		this._lineParts = null;
		this._charOffsetInPart = [];
		this._lastRenderedPartIndex = 0;
	}

A
Alex Dima 已提交
51 52
	// --- begin IVisibleLineData

E
Erich Gamma 已提交
53
	public getDomNode(): HTMLElement {
A
Alex Dima 已提交
54 55 56 57
		if (!this._domNode) {
			return null;
		}
		return this._domNode.domNode;
E
Erich Gamma 已提交
58 59
	}
	public setDomNode(domNode:HTMLElement): void {
A
Alex Dima 已提交
60
		this._domNode = createFastDomNode(domNode);
E
Erich Gamma 已提交
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
	}

	public onContentChanged(): void {
		this._isInvalid = true;
	}
	public onLinesInsertedAbove(): void {
		this._isMaybeInvalid = true;
	}
	public onLinesDeletedAbove(): void {
		this._isMaybeInvalid = true;
	}
	public onLineChangedAbove(): void {
		this._isMaybeInvalid = true;
	}
	public onTokensChanged(): void {
		this._isMaybeInvalid = true;
	}
	public onModelDecorationsChanged(): void {
		this._isMaybeInvalid = true;
	}
A
Alex Dima 已提交
81
	public onConfigurationChanged(e:IConfigurationChangedEvent): void {
A
Alex Dima 已提交
82 83 84
		if (e.renderWhitespace) {
			this._renderWhitespace = this._context.configuration.editor.renderWhitespace;
		}
85 86 87
		if (e.spaceWidth) {
			this._spaceWidth = this._context.configuration.editor.spaceWidth;
		}
A
Alex Dima 已提交
88 89 90 91 92 93 94 95 96
		if (e.lineHeight) {
			this._lineHeight = this._context.configuration.editor.lineHeight;
		}
		if (e.stopRenderingLineAfter) {
			this._stopRenderingLineAfter = this._context.configuration.editor.stopRenderingLineAfter;
		}
		if (e.fontLigatures) {
			this._fontLigatures = this._context.configuration.editor.fontLigatures;
		}
E
Erich Gamma 已提交
97 98 99
		this._isInvalid = true;
	}

A
Alex Dima 已提交
100
	public shouldUpdateHTML(startLineNumber:number, lineNumber:number, inlineDecorations:IModelDecoration[]): boolean {
A
Alex Dima 已提交
101
		let newLineParts:ILineParts = null;
E
Erich Gamma 已提交
102 103 104

		if (this._isMaybeInvalid || this._isInvalid) {
			// Compute new line parts only if there is some evidence that something might have changed
A
Alex Dima 已提交
105 106
			newLineParts = createLineParts(
				lineNumber,
107
				this._context.model.getLineMinColumn(lineNumber),
A
Alex Dima 已提交
108
				this._context.model.getLineContent(lineNumber),
109
				this._context.model.getTabSize(),
A
Alex Dima 已提交
110 111
				this._context.model.getLineTokens(lineNumber),
				inlineDecorations,
A
Alex Dima 已提交
112
				this._renderWhitespace
A
Alex Dima 已提交
113
			);
E
Erich Gamma 已提交
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
		}

		// Decide if isMaybeInvalid flips isInvalid to true
		if (this._isMaybeInvalid) {
			if (!this._isInvalid) {
				if (!this._lineParts || !this._lineParts.equals(newLineParts)) {
					this._isInvalid = true;
				}
			}
			this._isMaybeInvalid = false;
		}

		if (this._isInvalid) {
			this._lineParts = newLineParts;
		}

		return this._isInvalid;
	}

	public getLineOuterHTML(out:string[], lineNumber:number, deltaTop:number): void {
		out.push('<div lineNumber="');
		out.push(lineNumber.toString());
		out.push('" style="top:');
		out.push(deltaTop.toString());
		out.push('px;height:');
A
Alex Dima 已提交
139
		out.push(this._lineHeight.toString());
E
Erich Gamma 已提交
140
		out.push('px;" class="');
A
Alex Dima 已提交
141
		out.push(ClassNames.VIEW_LINE);
E
Erich Gamma 已提交
142 143 144 145 146 147 148
		out.push('">');
		out.push(this.getLineInnerHTML(lineNumber));
		out.push('</div>');
	}

	public getLineInnerHTML(lineNumber: number): string {
		this._isInvalid = false;
A
Alex Dima 已提交
149
		return this._render(lineNumber, this._lineParts);
E
Erich Gamma 已提交
150 151 152
	}

	public layoutLine(lineNumber:number, deltaTop:number): void {
A
Alex Dima 已提交
153 154
		this._domNode.setLineNumber(String(lineNumber));
		this._domNode.setTop(deltaTop);
A
Alex Dima 已提交
155
		this._domNode.setHeight(this._lineHeight);
E
Erich Gamma 已提交
156 157
	}

A
Alex Dima 已提交
158 159
	// --- end IVisibleLineData

A
Alex Dima 已提交
160
	private _render(lineNumber:number, lineParts:ILineParts): string {
E
Erich Gamma 已提交
161

A
Alex Dima 已提交
162
		this._cachedWidth = -1;
E
Erich Gamma 已提交
163

A
Alex Dima 已提交
164 165 166
		let r = renderLine(new RenderLineInput(
			this._context.model.getLineContent(lineNumber),
			this._context.model.getTabSize(),
167
			this._spaceWidth,
A
Alex Dima 已提交
168 169
			this._stopRenderingLineAfter,
			this._renderWhitespace,
A
Alex Dima 已提交
170 171
			lineParts.getParts()
		));
E
Erich Gamma 已提交
172 173 174 175 176 177 178 179 180

		this._charOffsetInPart = r.charOffsetInPart;
		this._lastRenderedPartIndex = r.lastRenderedPartIndex;

		return r.output;
	}

	// --- Reading from the DOM methods

181
	protected _getReadingTarget(): HTMLElement {
A
Alex Dima 已提交
182
		return <HTMLSpanElement>this._domNode.domNode.firstChild;
E
Erich Gamma 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
	}

	/**
	 * Width of the line in pixels
	 */
	public getWidth(): number {
		if (this._cachedWidth === -1) {
			this._cachedWidth = this._getReadingTarget().offsetWidth;
		}
		return this._cachedWidth;
	}

	/**
	 * Visible ranges for a model range
	 */
198
	public getVisibleRangesForRange(startColumn:number, endColumn:number, clientRectDeltaLeft:number, endNode:HTMLElement): HorizontalRange[] {
199 200 201 202
		startColumn = startColumn|0; // @perf
		endColumn = endColumn|0; // @perf
		clientRectDeltaLeft = clientRectDeltaLeft|0; // @perf
		const stopRenderingLineAfter = this._stopRenderingLineAfter|0; // @perf
E
Erich Gamma 已提交
203 204 205 206 207 208 209 210 211 212 213 214 215 216

		if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) {
			// This range is obviously not visible
			return null;
		}

		if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) {
			startColumn = stopRenderingLineAfter;
		}

		if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) {
			endColumn = stopRenderingLineAfter;
		}

217
		return this._readVisibleRangesForRange(startColumn, endColumn, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
218 219
	}

220
	protected _readVisibleRangesForRange(startColumn:number, endColumn:number, clientRectDeltaLeft:number, endNode:HTMLElement): HorizontalRange[] {
E
Erich Gamma 已提交
221
		if (startColumn === endColumn) {
222
			return this._readRawVisibleRangesForPosition(startColumn, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
223
		} else {
224
			return this._readRawVisibleRangesForRange(startColumn, endColumn, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
225 226 227
		}
	}

228
	protected _readRawVisibleRangesForPosition(column:number, clientRectDeltaLeft:number, endNode:HTMLElement): HorizontalRange[] {
E
Erich Gamma 已提交
229 230 231

		if (this._charOffsetInPart.length === 0) {
			// This line is empty
A
Alex Dima 已提交
232
			return [new HorizontalRange(0, 0)];
E
Erich Gamma 已提交
233 234
		}

A
Alex Dima 已提交
235 236
		let partIndex = findIndexInArrayWithMax(this._lineParts, column - 1, this._lastRenderedPartIndex);
		let charOffsetInPart = this._charOffsetInPart[column - 1];
E
Erich Gamma 已提交
237

238
		return RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, clientRectDeltaLeft, this._getScaleRatio(), endNode);
E
Erich Gamma 已提交
239 240
	}

241
	private _readRawVisibleRangesForRange(startColumn:number, endColumn:number, clientRectDeltaLeft:number, endNode:HTMLElement): HorizontalRange[] {
E
Erich Gamma 已提交
242 243 244 245

		if (startColumn === 1 && endColumn === this._charOffsetInPart.length) {
			// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line

A
Alex Dima 已提交
246
			return [this._readRawVisibleRangeForEntireLine()];
E
Erich Gamma 已提交
247 248
		}

A
Alex Dima 已提交
249 250 251 252
		let startPartIndex = findIndexInArrayWithMax(this._lineParts, startColumn - 1, this._lastRenderedPartIndex);
		let startCharOffsetInPart = this._charOffsetInPart[startColumn - 1];
		let endPartIndex = findIndexInArrayWithMax(this._lineParts, endColumn - 1, this._lastRenderedPartIndex);
		let endCharOffsetInPart = this._charOffsetInPart[endColumn - 1];
E
Erich Gamma 已提交
253

254
		return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, clientRectDeltaLeft, this._getScaleRatio(), endNode);
E
Erich Gamma 已提交
255 256
	}

A
Alex Dima 已提交
257 258
	private _readRawVisibleRangeForEntireLine(): HorizontalRange {
		return new HorizontalRange(0, this._getReadingTarget().offsetWidth);
E
Erich Gamma 已提交
259 260
	}

261 262
	protected _getScaleRatio(): number {
		return 1;
E
Erich Gamma 已提交
263 264
	}

A
Alex Dima 已提交
265 266 267
	/**
	 * Returns the column for the text found at a specific offset inside a rendered dom node
	 */
E
Erich Gamma 已提交
268
	public getColumnOfNodeOffset(lineNumber:number, spanNode:HTMLElement, offset:number): number {
A
Alex Dima 已提交
269
		let spanIndex = -1;
E
Erich Gamma 已提交
270 271 272 273
		while (spanNode) {
			spanNode = <HTMLElement>spanNode.previousSibling;
			spanIndex++;
		}
A
Alex Dima 已提交
274
		let lineParts = this._lineParts.getParts();
E
Erich Gamma 已提交
275 276

		if (spanIndex >= lineParts.length) {
A
Alex Dima 已提交
277
			return this._stopRenderingLineAfter;
E
Erich Gamma 已提交
278 279 280 281 282 283
		}

		if (offset === 0) {
			return lineParts[spanIndex].startIndex + 1;
		}

A
Alex Dima 已提交
284 285 286
		let originalMin = lineParts[spanIndex].startIndex;
		let originalMax:number;
		let originalMaxStartOffset:number;
E
Erich Gamma 已提交
287 288 289 290 291 292 293 294 295 296

		if (spanIndex + 1 < lineParts.length) {
			// Stop searching characters at the beginning of the next part
			originalMax = lineParts[spanIndex + 1].startIndex;
			originalMaxStartOffset = this._charOffsetInPart[originalMax - 1] + this._charOffsetInPart[originalMax];
		} else {
			originalMax = this._context.model.getLineMaxColumn(lineNumber) - 1;
			originalMaxStartOffset = this._charOffsetInPart[originalMax];
		}

A
Alex Dima 已提交
297 298
		let min = originalMin;
		let max = originalMax;
E
Erich Gamma 已提交
299

A
Alex Dima 已提交
300 301
		if (this._stopRenderingLineAfter !== -1) {
			max = Math.min(this._stopRenderingLineAfter - 1, originalMax);
E
Erich Gamma 已提交
302 303
		}

A
Alex Dima 已提交
304 305
		let nextStartOffset:number;
		let prevStartOffset:number;
E
Erich Gamma 已提交
306 307 308 309 310 311 312 313 314

		// Here are the variables and their relation plotted on an axis

		// prevStartOffset    a    midStartOffset    b    nextStartOffset
		// ------|------------|----------|-----------|-----------|--------->

		// Everything in (a;b] will match mid

		while (min < max) {
A
Alex Dima 已提交
315 316
			let mid = Math.floor( (min + max) / 2 );
			let midStartOffset = this._charOffsetInPart[mid];
E
Erich Gamma 已提交
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334

			if (mid === originalMax) {
				// Using Number.MAX_VALUE to ensure that any offset after midStartOffset will match mid
				nextStartOffset = Number.MAX_VALUE;
			} else if (mid + 1 === originalMax) {
				// mid + 1 is already in next part and might have the _charOffsetInPart = 0
				nextStartOffset = originalMaxStartOffset;
			} else {
				nextStartOffset = this._charOffsetInPart[mid + 1];
			}

			if (mid === originalMin) {
				// Using Number.MIN_VALUE to ensure that any offset before midStartOffset will match mid
				prevStartOffset = Number.MIN_VALUE;
			} else {
				prevStartOffset = this._charOffsetInPart[mid - 1];
			}

A
Alex Dima 已提交
335 336
			let a = (prevStartOffset + midStartOffset) / 2;
			let b = (midStartOffset + nextStartOffset) / 2;
E
Erich Gamma 已提交
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355

			if (a < offset && offset <= b) {
				// Hit!
				return mid + 1;
			}

			if (offset <= a) {
				max = mid - 1;
			} else {
				min = mid + 1;
			}
		}

		return min + 1;
	}
}

class IEViewLine extends ViewLine {

A
Alex Dima 已提交
356
	constructor(context:IViewContext) {
E
Erich Gamma 已提交
357 358 359
		super(context);
	}

360 361
	protected _getScaleRatio(): number {
		return screen.logicalXDPI / screen.deviceXDPI;
E
Erich Gamma 已提交
362 363 364 365 366
	}
}

class WebKitViewLine extends ViewLine {

A
Alex Dima 已提交
367
	constructor(context:IViewContext) {
E
Erich Gamma 已提交
368 369 370
		super(context);
	}

371 372
	protected _readVisibleRangesForRange(startColumn:number, endColumn:number, clientRectDeltaLeft:number, endNode:HTMLElement): HorizontalRange[] {
		let output = super._readVisibleRangesForRange(startColumn, endColumn, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
373

A
Alex Dima 已提交
374
		if (this._fontLigatures && output.length === 1 && endColumn > 1 && endColumn === this._charOffsetInPart.length) {
375 376 377 378 379 380 381
			let lastSpanBoundingClientRect = (<HTMLElement>this._getReadingTarget().lastChild).getBoundingClientRect();
			let lastSpanBoundingClientRectRight = lastSpanBoundingClientRect.right - clientRectDeltaLeft;
			if (startColumn === endColumn) {
				output[0].left = lastSpanBoundingClientRectRight;
				output[0].width = 0;
			} else {
				output[0].width = lastSpanBoundingClientRectRight - output[0].left;
382
			}
383
			return output;
384 385
		}

E
Erich Gamma 已提交
386 387 388 389
		if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._charOffsetInPart.length)) {
			return output;
		}

390
		// WebKit is buggy and returns an expanded range (to contain words in some cases)
E
Erich Gamma 已提交
391 392 393 394
		// The last client rect is enlarged (I think)

		// This is an attempt to patch things up
		// Find position of previous column
395
		let beforeEndVisibleRanges = this._readRawVisibleRangesForPosition(endColumn - 1, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
396
		// Find position of last column
397
		let endVisibleRanges = this._readRawVisibleRangesForPosition(endColumn, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
398 399

		if (beforeEndVisibleRanges && beforeEndVisibleRanges.length > 0 && endVisibleRanges && endVisibleRanges.length > 0) {
A
Alex Dima 已提交
400 401 402 403
			let beforeEndVisibleRange = beforeEndVisibleRanges[0];
			let endVisibleRange = endVisibleRanges[0];
			let isLTR = (beforeEndVisibleRange.left <= endVisibleRange.left);
			let lastRange = output[output.length - 1];
E
Erich Gamma 已提交
404

A
Andre Weinand 已提交
405
			if (isLTR && lastRange.left < endVisibleRange.left) {
E
Erich Gamma 已提交
406 407 408 409 410 411 412 413 414 415 416 417 418
				// Trim down the width of the last visible range to not go after the last column's position
				lastRange.width = endVisibleRange.left - lastRange.left;
			}
		}

		return output;
	}
}

class RangeUtil {

	/**
	 * Reusing the same range here
419
	 * because IE is buggy and constantly freezes when using a large number
E
Erich Gamma 已提交
420 421 422 423
	 * of ranges and calling .detach on them
	 */
	private static _handyReadyRange:Range;

424 425 426
	private static _createRange(): Range {
		if (!this._handyReadyRange) {
			this._handyReadyRange = document.createRange();
E
Erich Gamma 已提交
427
		}
428
		return this._handyReadyRange;
E
Erich Gamma 已提交
429 430
	}

431
	private static _detachRange(range:Range, endNode:HTMLElement): void {
E
Erich Gamma 已提交
432 433 434 435 436
		// 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);
	}

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
	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);
	}
E
Erich Gamma 已提交
515 516 517
}

function findIndexInArrayWithMax(lineParts:ILineParts, desiredIndex: number, maxResult:number): number {
A
Alex Dima 已提交
518
	let r = lineParts.findIndexOfOffset(desiredIndex);
E
Erich Gamma 已提交
519 520 521
	return r <= maxResult ? r : maxResult;
}

A
Alex Dima 已提交
522
export let createLine: (context: IViewContext) => ViewLine = (function() {
E
Erich Gamma 已提交
523 524 525 526
	if (window.screen && window.screen.deviceXDPI && (navigator.userAgent.indexOf('Trident/6.0') >= 0 || navigator.userAgent.indexOf('Trident/5.0') >= 0)) {
		// IE11 doesn't need the screen.logicalXDPI / screen.deviceXDPI ratio multiplication
		// for TextRange.getClientRects() anymore
		return createIELine;
A
Alex Dima 已提交
527
	} else if (browser.isWebKit) {
E
Erich Gamma 已提交
528 529 530 531 532
		return createWebKitLine;
	}
	return createNormalLine;
})();

A
Alex Dima 已提交
533
function createIELine(context: IViewContext): ViewLine {
E
Erich Gamma 已提交
534 535 536
	return new IEViewLine(context);
}

A
Alex Dima 已提交
537
function createWebKitLine(context: IViewContext): ViewLine {
E
Erich Gamma 已提交
538 539 540
	return new WebKitViewLine(context);
}

A
Alex Dima 已提交
541
function createNormalLine(context: IViewContext): ViewLine {
E
Erich Gamma 已提交
542 543 544
	return new ViewLine(context);
}