viewLine.ts 12.6 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';
A
Alex Dima 已提交
10
import {LineParts, createLineParts, getColumnOfLinePartOffset} 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';
A
Alex Dima 已提交
14
import {RangeUtil} from 'vs/editor/browser/viewParts/lines/rangeUtil';
E
Erich Gamma 已提交
15

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

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

A
Alex Dima 已提交
25
	private _domNode: FastDomNode;
E
Erich Gamma 已提交
26

A
Alex Dima 已提交
27
	private _lineParts: LineParts;
E
Erich Gamma 已提交
28 29 30 31

	private _isInvalid: boolean;
	private _isMaybeInvalid: boolean;

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

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

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

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

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

	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 已提交
82
	public onConfigurationChanged(e:IConfigurationChangedEvent): void {
A
Alex Dima 已提交
83 84 85
		if (e.renderWhitespace) {
			this._renderWhitespace = this._context.configuration.editor.renderWhitespace;
		}
86 87 88
		if (e.spaceWidth) {
			this._spaceWidth = this._context.configuration.editor.spaceWidth;
		}
A
Alex Dima 已提交
89 90 91 92 93 94 95 96 97
		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 已提交
98 99 100
		this._isInvalid = true;
	}

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

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

		// 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 已提交
140
		out.push(this._lineHeight.toString());
E
Erich Gamma 已提交
141
		out.push('px;" class="');
A
Alex Dima 已提交
142
		out.push(ClassNames.VIEW_LINE);
E
Erich Gamma 已提交
143 144 145 146 147 148 149
		out.push('">');
		out.push(this.getLineInnerHTML(lineNumber));
		out.push('</div>');
	}

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

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

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

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

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

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

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

		return r.output;
	}

	// --- Reading from the DOM methods

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

	/**
	 * 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
	 */
199
	public getVisibleRangesForRange(startColumn:number, endColumn:number, clientRectDeltaLeft:number, endNode:HTMLElement): HorizontalRange[] {
200 201 202 203
		startColumn = startColumn|0; // @perf
		endColumn = endColumn|0; // @perf
		clientRectDeltaLeft = clientRectDeltaLeft|0; // @perf
		const stopRenderingLineAfter = this._stopRenderingLineAfter|0; // @perf
E
Erich Gamma 已提交
204 205 206 207 208 209 210 211 212 213 214 215 216 217

		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;
		}

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

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

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

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

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

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

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

		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 已提交
247
			return [new HorizontalRange(0, this.getWidth())];
E
Erich Gamma 已提交
248 249
		}

A
Alex Dima 已提交
250 251 252 253
		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 已提交
254

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

258 259
	protected _getScaleRatio(): number {
		return 1;
E
Erich Gamma 已提交
260 261
	}

A
Alex Dima 已提交
262 263 264
	/**
	 * Returns the column for the text found at a specific offset inside a rendered dom node
	 */
E
Erich Gamma 已提交
265
	public getColumnOfNodeOffset(lineNumber:number, spanNode:HTMLElement, offset:number): number {
A
Alex Dima 已提交
266 267
		let spanNodeTextContentLength = spanNode.textContent.length;

A
Alex Dima 已提交
268
		let spanIndex = -1;
E
Erich Gamma 已提交
269 270 271 272
		while (spanNode) {
			spanNode = <HTMLElement>spanNode.previousSibling;
			spanIndex++;
		}
A
Alex Dima 已提交
273
		let lineParts = this._lineParts.getParts();
E
Erich Gamma 已提交
274

A
Alex Dima 已提交
275 276 277 278 279 280 281 282 283
		return getColumnOfLinePartOffset(
			this._stopRenderingLineAfter,
			lineParts,
			this._context.model.getLineMaxColumn(lineNumber),
			this._charOffsetInPart,
			spanIndex,
			spanNodeTextContentLength,
			offset
		);
E
Erich Gamma 已提交
284 285 286 287 288
	}
}

class IEViewLine extends ViewLine {

A
Alex Dima 已提交
289
	constructor(context:IViewContext) {
E
Erich Gamma 已提交
290 291 292
		super(context);
	}

293 294
	protected _getScaleRatio(): number {
		return screen.logicalXDPI / screen.deviceXDPI;
E
Erich Gamma 已提交
295 296 297 298 299
	}
}

class WebKitViewLine extends ViewLine {

A
Alex Dima 已提交
300
	constructor(context:IViewContext) {
E
Erich Gamma 已提交
301 302 303
		super(context);
	}

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

A
Alex Dima 已提交
307
		if (this._fontLigatures && output.length === 1 && endColumn > 1 && endColumn === this._charOffsetInPart.length) {
308 309 310 311 312 313 314
			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;
315
			}
316
			return output;
317 318
		}

E
Erich Gamma 已提交
319 320 321 322
		if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._charOffsetInPart.length)) {
			return output;
		}

323
		// WebKit is buggy and returns an expanded range (to contain words in some cases)
E
Erich Gamma 已提交
324 325 326 327
		// The last client rect is enlarged (I think)

		// This is an attempt to patch things up
		// Find position of previous column
328
		let beforeEndVisibleRanges = this._readRawVisibleRangesForPosition(endColumn - 1, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
329
		// Find position of last column
330
		let endVisibleRanges = this._readRawVisibleRangesForPosition(endColumn, clientRectDeltaLeft, endNode);
E
Erich Gamma 已提交
331 332

		if (beforeEndVisibleRanges && beforeEndVisibleRanges.length > 0 && endVisibleRanges && endVisibleRanges.length > 0) {
A
Alex Dima 已提交
333 334 335 336
			let beforeEndVisibleRange = beforeEndVisibleRanges[0];
			let endVisibleRange = endVisibleRanges[0];
			let isLTR = (beforeEndVisibleRange.left <= endVisibleRange.left);
			let lastRange = output[output.length - 1];
E
Erich Gamma 已提交
337

A
Andre Weinand 已提交
338
			if (isLTR && lastRange.left < endVisibleRange.left) {
E
Erich Gamma 已提交
339 340 341 342 343 344 345 346 347 348
				// 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;
	}
}


A
Alex Dima 已提交
349
function findIndexInArrayWithMax(lineParts:LineParts, desiredIndex: number, maxResult:number): number {
A
Alex Dima 已提交
350
	let r = lineParts.findIndexOfOffset(desiredIndex);
E
Erich Gamma 已提交
351 352 353
	return r <= maxResult ? r : maxResult;
}

A
Alex Dima 已提交
354
export let createLine: (context: IViewContext) => ViewLine = (function() {
E
Erich Gamma 已提交
355 356 357 358
	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 已提交
359
	} else if (browser.isWebKit) {
E
Erich Gamma 已提交
360 361 362 363 364
		return createWebKitLine;
	}
	return createNormalLine;
})();

A
Alex Dima 已提交
365
function createIELine(context: IViewContext): ViewLine {
E
Erich Gamma 已提交
366 367 368
	return new IEViewLine(context);
}

A
Alex Dima 已提交
369
function createWebKitLine(context: IViewContext): ViewLine {
E
Erich Gamma 已提交
370 371 372
	return new WebKitViewLine(context);
}

A
Alex Dima 已提交
373
function createNormalLine(context: IViewContext): ViewLine {
E
Erich Gamma 已提交
374 375 376
	return new ViewLine(context);
}