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

J
Johannes Rieken 已提交
6
import { CharCode } from 'vs/base/common/charCode';
A
Alex Dima 已提交
7
import * as strings from 'vs/base/common/strings';
8
import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
J
Johannes Rieken 已提交
9
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
10
import { ILineMapperFactory, LineBreakingData, ILineMappingComputer } from 'vs/editor/common/viewModel/splitLinesCollection';
E
Erich Gamma 已提交
11

12
const enum CharacterClass {
13 14 15
	NONE = 0,
	BREAK_BEFORE = 1,
	BREAK_AFTER = 2,
16
	BREAK_IDEOGRAPHIC = 3 // for Han and Kana.
17
}
E
Erich Gamma 已提交
18

19
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
20

21
	constructor(BREAK_BEFORE: string, BREAK_AFTER: string) {
22
		super(CharacterClass.NONE);
23 24

		for (let i = 0; i < BREAK_BEFORE.length; i++) {
25
			this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
26
		}
E
Erich Gamma 已提交
27

28
		for (let i = 0; i < BREAK_AFTER.length; i++) {
29
			this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
30
		}
E
Erich Gamma 已提交
31 32
	}

J
Johannes Rieken 已提交
33
	public get(charCode: number): CharacterClass {
34 35 36 37 38 39 40 41 42 43 44 45 46 47
		if (charCode >= 0 && charCode < 256) {
			return <CharacterClass>this._asciiMap[charCode];
		} else {
			// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
			// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
			// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
			// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
			if (
				(charCode >= 0x3040 && charCode <= 0x30FF)
				|| (charCode >= 0x3400 && charCode <= 0x4DBF)
				|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
			) {
				return CharacterClass.BREAK_IDEOGRAPHIC;
			}
48

49 50
			return <CharacterClass>(this._map.get(charCode) || this._defaultValue);
		}
51
	}
E
Erich Gamma 已提交
52 53 54 55
}

export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactory {

56 57 58
	public static create(options: IComputedEditorOptions): CharacterHardWrappingLineMapperFactory {
		return new CharacterHardWrappingLineMapperFactory(
			options.get(EditorOption.wordWrapBreakBeforeCharacters),
59
			options.get(EditorOption.wordWrapBreakAfterCharacters)
60 61 62
		);
	}

63
	private readonly classifier: WrappingCharacterClassifier;
E
Erich Gamma 已提交
64

65 66
	constructor(breakBeforeChars: string, breakAfterChars: string) {
		this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars);
E
Erich Gamma 已提交
67 68
	}

69
	public createLineMappingComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMappingComputer {
70 71 72 73
		tabSize = tabSize | 0; //@perf
		wrappingColumn = +wrappingColumn; //@perf
		columnsForFullWidthChar = +columnsForFullWidthChar; //@perf

74
		let requests: string[] = [];
75
		let previousBreakingData: (LineBreakingData | null)[] = [];
76
		return {
77
			addRequest: (lineText: string, previousLineBreakingData: LineBreakingData | null) => {
78
				requests.push(lineText);
79
				previousBreakingData.push(previousLineBreakingData);
80 81
			},
			finalize: () => {
82
				let result: (LineBreakingData | null)[] = [];
83
				for (let i = 0, len = requests.length; i < len; i++) {
A
Alexandru Dima 已提交
84
					result[i] = createLineMapping(this.classifier, previousBreakingData[i], requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
85 86 87 88 89
				}
				return result;
			}
		};
	}
A
Alexandru Dima 已提交
90
}
91

A
Alexandru Dima 已提交
92 93 94 95
function createLineMapping(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakingData | null, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null {
	if (firstLineBreakingColumn === -1) {
		return null;
	}
96

A
Alexandru Dima 已提交
97 98 99 100 101
	const len = lineText.length;
	if (len <= 1) {
		return null;
	}

A
Alexandru Dima 已提交
102 103 104 105 106 107 108 109 110 111
	const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent);
	const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength;

	let breakingOffsets: number[] = [];
	let breakingOffsetsVisibleColumn: number[] = [];
	let breakingOffsetsCount: number = 0;
	let breakOffset = 0;
	let breakOffsetVisibleColumn = 0;

	let breakingColumn = firstLineBreakingColumn;
A
Alexandru Dima 已提交
112 113 114
	let prevCharCode = lineText.charCodeAt(0);
	let prevCharCodeClass = classifier.get(prevCharCode);
	let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar);
A
Alexandru Dima 已提交
115

A
Alexandru Dima 已提交
116 117 118
	for (let i = 1; i < len; i++) {
		const charCode = lineText.charCodeAt(i);
		const charCodeClass = classifier.get(charCode);
A
Alexandru Dima 已提交
119

A
Alexandru Dima 已提交
120
		if (strings.isHighSurrogate(prevCharCode)) {
A
Alexandru Dima 已提交
121 122
			// A surrogate pair must always be considered as a single unit, so it is never to be broken
			visibleColumn += 1;
A
Alexandru Dima 已提交
123 124
			prevCharCode = charCode;
			prevCharCodeClass = charCodeClass;
A
Alexandru Dima 已提交
125 126
			continue;
		}
E
Erich Gamma 已提交
127

A
Alexandru Dima 已提交
128
		if (canBreak(prevCharCodeClass, charCodeClass)) {
A
Alexandru Dima 已提交
129 130 131
			breakOffset = i;
			breakOffsetVisibleColumn = visibleColumn;
		}
E
Erich Gamma 已提交
132

A
Alexandru Dima 已提交
133 134
		const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
		visibleColumn += charWidth;
E
Erich Gamma 已提交
135

A
Alexandru Dima 已提交
136
		// check if adding character at `i` will go over the breaking column
A
Alexandru Dima 已提交
137
		if (visibleColumn > breakingColumn) {
A
Alexandru Dima 已提交
138
			// We need to break at least before character at `i`:
E
Erich Gamma 已提交
139

A
Alexandru Dima 已提交
140 141 142
			if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) {
				// Cannot break at `breakOffset`, must break at `i`
				breakOffset = i;
A
Alexandru Dima 已提交
143
				breakOffsetVisibleColumn = visibleColumn - charWidth;
E
Erich Gamma 已提交
144 145
			}

A
Alexandru Dima 已提交
146 147 148 149 150
			breakingOffsets[breakingOffsetsCount] = breakOffset;
			breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
			breakingOffsetsCount++;
			breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn;
			breakOffset = 0;
E
Erich Gamma 已提交
151 152
		}

A
Alexandru Dima 已提交
153 154
		prevCharCode = charCode;
		prevCharCodeClass = charCodeClass;
A
Alexandru Dima 已提交
155
	}
E
Erich Gamma 已提交
156

A
Alexandru Dima 已提交
157 158
	if (breakingOffsetsCount === 0) {
		return null;
E
Erich Gamma 已提交
159
	}
A
Alexandru Dima 已提交
160 161 162 163 164

	// Add last segment
	breakingOffsets[breakingOffsetsCount] = len;
	breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn;

A
Alexandru Dima 已提交
165 166 167 168 169 170 171 172 173 174 175
	return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
}

function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number {
	if (charCode === CharCode.Tab) {
		return (tabSize - (visibleColumn % tabSize));
	}
	if (strings.isFullWidthCharacter(charCode)) {
		return columnsForFullWidthChar;
	}
	return 1;
B
Belleve Invis 已提交
176
}
177 178 179 180

function tabCharacterWidth(visibleColumn: number, tabSize: number): number {
	return (tabSize - (visibleColumn % tabSize));
}
A
Alexandru Dima 已提交
181

A
Alexandru Dima 已提交
182 183 184 185 186
/**
 * Kinsoku Shori : Don't break after a leading character, like an open bracket
 * Kinsoku Shori : Don't break before a trailing character, like a period
 */
function canBreak(prevCharCodeClass: CharacterClass, charCodeClass: CharacterClass): boolean {
A
Alexandru Dima 已提交
187
	return (
A
Alexandru Dima 已提交
188 189 190
		(prevCharCodeClass === CharacterClass.BREAK_AFTER)
		|| (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
		|| (charCodeClass === CharacterClass.BREAK_BEFORE)
A
Alexandru Dima 已提交
191 192 193 194
		|| (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
	);
}

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): number {
	let wrappedTextIndentLength = 0;
	if (hardWrappingIndent !== WrappingIndent.None) {
		const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
		if (firstNonWhitespaceIndex !== -1) {
			// Track existing indent

			for (let i = 0; i < firstNonWhitespaceIndex; i++) {
				const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1);
				wrappedTextIndentLength += charWidth;
			}

			// Increase indent of continuation lines, if desired
			const numberOfAdditionalTabs = (hardWrappingIndent === WrappingIndent.DeepIndent ? 2 : hardWrappingIndent === WrappingIndent.Indent ? 1 : 0);
			for (let i = 0; i < numberOfAdditionalTabs; i++) {
				const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize);
				wrappedTextIndentLength += charWidth;
			}

			// Force sticking to beginning of line if no character would fit except for the indentation
			if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakingColumn) {
				wrappedTextIndentLength = 0;
			}
		}
	}
	return wrappedTextIndentLength;
}