viewLineRenderer.ts 7.3 KB
Newer Older
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 {ViewLineToken} from 'vs/editor/common/core/viewLineToken';
A
Alex Dima 已提交
8 9

export class RenderLineInput {
A
Alex Dima 已提交
10
	_renderLineInputBrand: void;
11 12 13

	lineContent: string;
	tabSize: number;
14
	spaceWidth: number;
15
	stopRenderingLineAfter: number;
16
	renderWhitespace: 'none' | 'boundary' | 'all';
17
	renderControlCharacters: boolean;
18
	parts: ViewLineToken[];
A
Alex Dima 已提交
19 20 21 22

	constructor(
		lineContent: string,
		tabSize: number,
23
		spaceWidth: number,
A
Alex Dima 已提交
24
		stopRenderingLineAfter: number,
25
		renderWhitespace: 'none' | 'boundary' | 'all',
26
		renderControlCharacters: boolean,
27
		parts: ViewLineToken[]
A
Alex Dima 已提交
28 29 30
	) {
		this.lineContent = lineContent;
		this.tabSize = tabSize;
31
		this.spaceWidth = spaceWidth;
A
Alex Dima 已提交
32 33
		this.stopRenderingLineAfter = stopRenderingLineAfter;
		this.renderWhitespace = renderWhitespace;
34
		this.renderControlCharacters = renderControlCharacters;
A
Alex Dima 已提交
35 36
		this.parts = parts;
	}
37 38
}

A
Alex Dima 已提交
39
export class RenderLineOutput {
A
Alex Dima 已提交
40
	_renderLineOutputBrand: void;
41 42
	charOffsetInPart: number[];
	lastRenderedPartIndex: number;
A
Alex Dima 已提交
43
	output: string;
A
Alex Dima 已提交
44 45 46 47 48 49

	constructor(charOffsetInPart: number[], lastRenderedPartIndex: number, output: string) {
		this.charOffsetInPart = charOffsetInPart;
		this.lastRenderedPartIndex = lastRenderedPartIndex;
		this.output = output;
	}
50 51
}

A
Alex Dima 已提交
52 53 54 55 56 57
const _space = ' '.charCodeAt(0);
const _tab = '\t'.charCodeAt(0);
const _lowerThan = '<'.charCodeAt(0);
const _greaterThan = '>'.charCodeAt(0);
const _ampersand = '&'.charCodeAt(0);
const _carriageReturn = '\r'.charCodeAt(0);
58
const _controlCharacterSequenceConversionStart = 9216;
A
Alex Dima 已提交
59 60
const _lineSeparator = '\u2028'.charCodeAt(0); //http://www.fileformat.info/info/unicode/char/2028/index.htm
const _bom = 65279;
61

A
Alex Dima 已提交
62
export function renderLine(input:RenderLineInput): RenderLineOutput {
63
	const lineText = input.lineContent;
A
Alex Dima 已提交
64
	const lineTextLength = lineText.length;
65
	const tabSize = input.tabSize;
66
	const spaceWidth = input.spaceWidth;
A
Alex Dima 已提交
67 68
	const actualLineParts = input.parts;
	const renderWhitespace = input.renderWhitespace;
69
	const renderControlCharacters = input.renderControlCharacters;
A
Alex Dima 已提交
70
	const charBreakIndex = (input.stopRenderingLineAfter === -1 ? lineTextLength : input.stopRenderingLineAfter - 1);
71 72

	if (lineTextLength === 0) {
A
Alex Dima 已提交
73 74 75
		return new RenderLineOutput(
			[],
			0,
76
			// This is basically for IE's hit test to work
A
Alex Dima 已提交
77 78
			'<span><span>&nbsp;</span></span>'
		);
79 80 81 82 83 84
	}

	if (actualLineParts.length === 0) {
		throw new Error('Cannot render non empty line without line parts!');
	}

85
	return renderLineActual(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts.slice(0), renderWhitespace, renderControlCharacters, charBreakIndex);
A
Alex Dima 已提交
86 87 88
}

function isWhitespace(type:string): boolean {
89 90 91
	return (type.indexOf('whitespace') >= 0);
}

92 93 94 95 96 97 98
function isControlCharacter(characterCode: number): boolean {
	return characterCode < 32;
}
function controlCharacterToPrintable(characterCode: number): string {
	return String.fromCharCode(_controlCharacterSequenceConversionStart + characterCode);
}

99
function renderLineActual(lineText: string, lineTextLength: number, tabSize: number, spaceWidth: number, actualLineParts: ViewLineToken[], renderWhitespace: 'none' | 'boundary' | 'all', renderControlCharacters: boolean, charBreakIndex: number): RenderLineOutput {
A
Alex Dima 已提交
100 101 102 103
	lineTextLength = +lineTextLength;
	tabSize = +tabSize;
	charBreakIndex = +charBreakIndex;

A
Alex Dima 已提交
104
	let charIndex = 0;
A
Alex Dima 已提交
105
	let out = '';
A
Alex Dima 已提交
106 107
	let charOffsetInPartArr: number[] = [];
	let charOffsetInPart = 0;
108
	let tabsCharDelta = 0;
109

A
Alex Dima 已提交
110
	out += '<span>';
A
Alex Dima 已提交
111 112 113
	for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) {
		let part = actualLineParts[partIndex];

114
		let parsRendersWhitespace = (renderWhitespace !== 'none' && isWhitespace(part.type));
115

A
Alex Dima 已提交
116 117
		let toCharIndex = lineTextLength;
		if (partIndex + 1 < partIndexLen) {
A
Alex Dima 已提交
118 119
			let nextPart = actualLineParts[partIndex + 1];
			toCharIndex = Math.min(lineTextLength, nextPart.startIndex);
120 121
		}

A
Alex Dima 已提交
122
		charOffsetInPart = 0;
123
		if (parsRendersWhitespace) {
124 125 126 127 128 129

			let partContentCnt = 0;
			let partContent = '';
			for (; charIndex < toCharIndex; charIndex++) {
				charOffsetInPartArr[charIndex] = charOffsetInPart;
				let charCode = lineText.charCodeAt(charIndex);
A
Alex Dima 已提交
130

131
				if (charCode === _tab) {
A
Alex Dima 已提交
132 133 134 135
					let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
					tabsCharDelta += insertSpacesCount - 1;
					charOffsetInPart += insertSpacesCount - 1;
					if (insertSpacesCount > 0) {
136
						partContent += '&rarr;';
137
						partContentCnt++;
A
Alex Dima 已提交
138 139 140
						insertSpacesCount--;
					}
					while (insertSpacesCount > 0) {
141 142
						partContent += '&nbsp;';
						partContentCnt++;
A
Alex Dima 已提交
143 144
						insertSpacesCount--;
					}
145 146
				} else {
					// must be _space
147
					partContent += '&middot;';
148 149 150 151 152 153 154 155 156 157
					partContentCnt++;
				}

				charOffsetInPart ++;

				if (charIndex >= charBreakIndex) {
					out += '<span class="token '+part.type+'" style="width:'+(spaceWidth * partContentCnt)+'px">';
					out += partContent;
					out += '&hellip;</span></span>';
					charOffsetInPartArr[charIndex] = charOffsetInPart;
A
Alex Dima 已提交
158 159 160 161 162
					return new RenderLineOutput(
						charOffsetInPartArr,
						partIndex,
						out
					);
163
				}
A
Alex Dima 已提交
164
			}
165 166 167 168 169 170 171 172 173
			out += '<span class="token '+part.type+'" style="width:'+(spaceWidth * partContentCnt)+'px">';
			out += partContent;
			out += '</span>';
		} else {
			out += '<span class="token ';
			out += part.type;
			out += '">';

			for (; charIndex < toCharIndex; charIndex++) {
A
Alex Dima 已提交
174
				charOffsetInPartArr[charIndex] = charOffsetInPart;
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
				let charCode = lineText.charCodeAt(charIndex);

				switch (charCode) {
					case _tab:
						let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
						tabsCharDelta += insertSpacesCount - 1;
						charOffsetInPart += insertSpacesCount - 1;
						while (insertSpacesCount > 0) {
							out += '&nbsp;';
							insertSpacesCount--;
						}
						break;

					case _space:
						out += '&nbsp;';
						break;

					case _lowerThan:
						out += '&lt;';
						break;

					case _greaterThan:
						out += '&gt;';
						break;

					case _ampersand:
						out += '&amp;';
						break;

					case 0:
						out += '&#00;';
						break;

					case _bom:
					case _lineSeparator:
						out += '\ufffd';
						break;

					case _carriageReturn:
						// zero width space, because carriage return would introduce a line break
						out += '&#8203';
						break;

					default:
219 220 221 222 223 224
						let characterCode = lineText.charCodeAt(charIndex);
						if (renderControlCharacters && isControlCharacter(characterCode)) {
							out += controlCharacterToPrintable(characterCode);
						} else {
							out += lineText.charAt(charIndex);
						}
225 226 227 228 229 230 231
				}

				charOffsetInPart ++;

				if (charIndex >= charBreakIndex) {
					out += '&hellip;</span></span>';
					charOffsetInPartArr[charIndex] = charOffsetInPart;
A
Alex Dima 已提交
232 233 234 235 236
					return new RenderLineOutput(
						charOffsetInPartArr,
						partIndex,
						out
					);
237
				}
A
Alex Dima 已提交
238
			}
239 240

			out += '</span>';
A
Alex Dima 已提交
241
		}
242

243
	}
A
Alex Dima 已提交
244
	out += '</span>';
245 246 247

	// When getting client rects for the last character, we will position the
	// text range at the end of the span, insteaf of at the beginning of next span
A
Alex Dima 已提交
248
	charOffsetInPartArr.push(charOffsetInPart);
249

A
Alex Dima 已提交
250 251 252 253 254
	return new RenderLineOutput(
		charOffsetInPartArr,
		actualLineParts.length - 1,
		out
	);
255
}