Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CharCode } from '../../../base/common/charCode.js';
7
import * as strings from '../../../base/common/strings.js';
8
import { WrappingIndent, IComputedEditorOptions, EditorOption } from '../config/editorOptions.js';
9
import { CharacterClassifier } from '../core/characterClassifier.js';
10
import { FontInfo } from '../config/fontInfo.js';
11
import { LineInjectedText } from '../textModelEvents.js';
12
import { InjectedTextOptions } from '../model.js';
13
import { ILineBreaksComputerFactory, ILineBreaksComputer, ModelLineProjectionData } from '../modelLineProjectionData.js';
14
15
export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory {
16
public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory {
17
return new MonospaceLineBreaksComputerFactory(
18
options.get(EditorOption.wordWrapBreakBeforeCharacters),
19
options.get(EditorOption.wordWrapBreakAfterCharacters)
20
);
21
}
22
23
private readonly classifier: WrappingCharacterClassifier;
24
25
constructor(breakBeforeChars: string, breakAfterChars: string) {
26
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars);
27
}
28
29
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', wrapOnEscapedLineFeeds: boolean): ILineBreaksComputer {
30
const requests: string[] = [];
31
const injectedTexts: (LineInjectedText[] | null)[] = [];
32
const previousBreakingData: (ModelLineProjectionData | null)[] = [];
33
return {
34
addRequest: (lineText: string, injectedText: LineInjectedText[] | null, previousLineBreakData: ModelLineProjectionData | null) => {
35
requests.push(lineText);
36
injectedTexts.push(injectedText);
37
previousBreakingData.push(previousLineBreakData);
38
},
39
finalize: () => {
40
const columnsForFullWidthChar = fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth;
41
const result: (ModelLineProjectionData | null)[] = [];
42
for (let i = 0, len = requests.length; i < len; i++) {
43
const injectedText = injectedTexts[i];
44
const previousLineBreakData = previousBreakingData[i];
45
if (previousLineBreakData && !previousLineBreakData.injectionOptions && !injectedText) {
46
result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak);
47
} else {
48
result[i] = createLineBreaks(this.classifier, requests[i], injectedText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak, wrapOnEscapedLineFeeds);
49
}
50
}
51
arrPool1.length = 0;
52
arrPool2.length = 0;
53
return result;
54
}
55
};
56
}
57
}
58
59
const enum CharacterClass {
60
NONE = 0,
61
BREAK_BEFORE = 1,
62
BREAK_AFTER = 2,
63
BREAK_IDEOGRAPHIC = 3 // for Han and Kana.
64
}
65
66
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
67
68
constructor(BREAK_BEFORE: string, BREAK_AFTER: string) {
69
super(CharacterClass.NONE);
70
71
for (let i = 0; i < BREAK_BEFORE.length; i++) {
72
this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
73
}
74
75
for (let i = 0; i < BREAK_AFTER.length; i++) {
76
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
77
}
78
}
79
80
public override get(charCode: number): CharacterClass {
81
if (charCode >= 0 && charCode < 256) {
82
return <CharacterClass>this._asciiMap[charCode];
83
} else {
84
// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
85
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
86
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
87
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
88
if (
89
(charCode >= 0x3040 && charCode <= 0x30FF)
90
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
91
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
92
) {
93
return CharacterClass.BREAK_IDEOGRAPHIC;
94
}
95
96
return <CharacterClass>(this._map.get(charCode) || this._defaultValue);
97
}
98
}
99
}
100
101
let arrPool1: number[] = [];
102
let arrPool2: number[] = [];
103
104
function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: ModelLineProjectionData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ModelLineProjectionData | null {
105
if (firstLineBreakColumn === -1) {
106
return null;
107
}
108
109
const len = lineText.length;
110
if (len <= 1) {
111
return null;
112
}
113
114
const isKeepAll = (wordBreak === 'keepAll');
115
116
const prevBreakingOffsets = previousBreakingData.breakOffsets;
117
const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakOffsetsVisibleColumn;
118
119
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
120
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
121
122
const breakingOffsets: number[] = arrPool1;
123
const breakingOffsetsVisibleColumn: number[] = arrPool2;
124
let breakingOffsetsCount = 0;
125
let lastBreakingOffset = 0;
126
let lastBreakingOffsetVisibleColumn = 0;
127
128
let breakingColumn = firstLineBreakColumn;
129
const prevLen = prevBreakingOffsets.length;
130
let prevIndex = 0;
131
132
if (prevIndex >= 0) {
133
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
134
while (prevIndex + 1 < prevLen) {
135
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
136
if (distance >= bestDistance) {
137
break;
138
}
139
bestDistance = distance;
140
prevIndex++;
141
}
142
}
143
144
while (prevIndex < prevLen) {
145
// Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break)
146
let prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
147
let prevBreakOffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
148
if (lastBreakingOffset > prevBreakOffset) {
149
prevBreakOffset = lastBreakingOffset;
150
prevBreakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn;
151
}
152
153
let breakOffset = 0;
154
let breakOffsetVisibleColumn = 0;
155
156
let forcedBreakOffset = 0;
157
let forcedBreakOffsetVisibleColumn = 0;
158
159
// initially, we search as much as possible to the right (if it fits)
160
if (prevBreakOffsetVisibleColumn <= breakingColumn) {
161
let visibleColumn = prevBreakOffsetVisibleColumn;
162
let prevCharCode = prevBreakOffset === 0 ? CharCode.Null : lineText.charCodeAt(prevBreakOffset - 1);
163
let prevCharCodeClass = prevBreakOffset === 0 ? CharacterClass.NONE : classifier.get(prevCharCode);
164
let entireLineFits = true;
165
for (let i = prevBreakOffset; i < len; i++) {
166
const charStartOffset = i;
167
const charCode = lineText.charCodeAt(i);
168
let charCodeClass: number;
169
let charWidth: number;
170
171
if (strings.isHighSurrogate(charCode)) {
172
// A surrogate pair must always be considered as a single unit, so it is never to be broken
173
i++;
174
charCodeClass = CharacterClass.NONE;
175
charWidth = 2;
176
} else {
177
charCodeClass = classifier.get(charCode);
178
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
179
}
180
181
if (charStartOffset > lastBreakingOffset && canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) {
182
breakOffset = charStartOffset;
183
breakOffsetVisibleColumn = visibleColumn;
184
}
185
186
visibleColumn += charWidth;
187
188
// check if adding character at `i` will go over the breaking column
189
if (visibleColumn > breakingColumn) {
190
// We need to break at least before character at `i`:
191
if (charStartOffset > lastBreakingOffset) {
192
forcedBreakOffset = charStartOffset;
193
forcedBreakOffsetVisibleColumn = visibleColumn - charWidth;
194
} else {
195
// we need to advance at least by one character
196
forcedBreakOffset = i + 1;
197
forcedBreakOffsetVisibleColumn = visibleColumn;
198
}
199
200
if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
201
// Cannot break at `breakOffset` => reset it if it was set
202
breakOffset = 0;
203
}
204
205
entireLineFits = false;
206
break;
207
}
208
209
prevCharCode = charCode;
210
prevCharCodeClass = charCodeClass;
211
}
212
213
if (entireLineFits) {
214
// there is no more need to break => stop the outer loop!
215
if (breakingOffsetsCount > 0) {
216
// Add last segment, no need to assign to `lastBreakingOffset` and `lastBreakingOffsetVisibleColumn`
217
breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1];
218
breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1];
219
breakingOffsetsCount++;
220
}
221
break;
222
}
223
}
224
225
if (breakOffset === 0) {
226
// must search left
227
let visibleColumn = prevBreakOffsetVisibleColumn;
228
let charCode = lineText.charCodeAt(prevBreakOffset);
229
let charCodeClass = classifier.get(charCode);
230
let hitATabCharacter = false;
231
for (let i = prevBreakOffset - 1; i >= lastBreakingOffset; i--) {
232
const charStartOffset = i + 1;
233
const prevCharCode = lineText.charCodeAt(i);
234
235
if (prevCharCode === CharCode.Tab) {
236
// cannot determine the width of a tab when going backwards, so we must go forwards
237
hitATabCharacter = true;
238
break;
239
}
240
241
let prevCharCodeClass: number;
242
let prevCharWidth: number;
243
244
if (strings.isLowSurrogate(prevCharCode)) {
245
// A surrogate pair must always be considered as a single unit, so it is never to be broken
246
i--;
247
prevCharCodeClass = CharacterClass.NONE;
248
prevCharWidth = 2;
249
} else {
250
prevCharCodeClass = classifier.get(prevCharCode);
251
prevCharWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1);
252
}
253
254
if (visibleColumn <= breakingColumn) {
255
if (forcedBreakOffset === 0) {
256
forcedBreakOffset = charStartOffset;
257
forcedBreakOffsetVisibleColumn = visibleColumn;
258
}
259
260
if (visibleColumn <= breakingColumn - wrappedLineBreakColumn) {
261
// went too far!
262
break;
263
}
264
265
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) {
266
breakOffset = charStartOffset;
267
breakOffsetVisibleColumn = visibleColumn;
268
break;
269
}
270
}
271
272
visibleColumn -= prevCharWidth;
273
charCode = prevCharCode;
274
charCodeClass = prevCharCodeClass;
275
}
276
277
if (breakOffset !== 0) {
278
const remainingWidthOfNextLine = wrappedLineBreakColumn - (forcedBreakOffsetVisibleColumn - breakOffsetVisibleColumn);
279
if (remainingWidthOfNextLine <= tabSize) {
280
const charCodeAtForcedBreakOffset = lineText.charCodeAt(forcedBreakOffset);
281
let charWidth: number;
282
if (strings.isHighSurrogate(charCodeAtForcedBreakOffset)) {
283
// A surrogate pair must always be considered as a single unit, so it is never to be broken
284
charWidth = 2;
285
} else {
286
charWidth = computeCharWidth(charCodeAtForcedBreakOffset, forcedBreakOffsetVisibleColumn, tabSize, columnsForFullWidthChar);
287
}
288
if (remainingWidthOfNextLine - charWidth < 0) {
289
// it is not worth it to break at breakOffset, it just introduces an extra needless line!
290
breakOffset = 0;
291
}
292
}
293
}
294
295
if (hitATabCharacter) {
296
// cannot determine the width of a tab when going backwards, so we must go forwards from the previous break
297
prevIndex--;
298
continue;
299
}
300
}
301
302
if (breakOffset === 0) {
303
// Could not find a good breaking point
304
breakOffset = forcedBreakOffset;
305
breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn;
306
}
307
308
if (breakOffset <= lastBreakingOffset) {
309
// Make sure that we are advancing (at least one character)
310
const charCode = lineText.charCodeAt(lastBreakingOffset);
311
if (strings.isHighSurrogate(charCode)) {
312
// A surrogate pair must always be considered as a single unit, so it is never to be broken
313
breakOffset = lastBreakingOffset + 2;
314
breakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn + 2;
315
} else {
316
breakOffset = lastBreakingOffset + 1;
317
breakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn + computeCharWidth(charCode, lastBreakingOffsetVisibleColumn, tabSize, columnsForFullWidthChar);
318
}
319
}
320
321
lastBreakingOffset = breakOffset;
322
breakingOffsets[breakingOffsetsCount] = breakOffset;
323
lastBreakingOffsetVisibleColumn = breakOffsetVisibleColumn;
324
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
325
breakingOffsetsCount++;
326
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
327
328
while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) {
329
prevIndex++;
330
}
331
332
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
333
while (prevIndex + 1 < prevLen) {
334
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
335
if (distance >= bestDistance) {
336
break;
337
}
338
bestDistance = distance;
339
prevIndex++;
340
}
341
}
342
343
if (breakingOffsetsCount === 0) {
344
return null;
345
}
346
347
// Doing here some object reuse which ends up helping a huge deal with GC pauses!
348
breakingOffsets.length = breakingOffsetsCount;
349
breakingOffsetsVisibleColumn.length = breakingOffsetsCount;
350
arrPool1 = previousBreakingData.breakOffsets;
351
arrPool2 = previousBreakingData.breakOffsetsVisibleColumn;
352
previousBreakingData.breakOffsets = breakingOffsets;
353
previousBreakingData.breakOffsetsVisibleColumn = breakingOffsetsVisibleColumn;
354
previousBreakingData.wrappedTextIndentLength = wrappedTextIndentLength;
355
return previousBreakingData;
356
}
357
358
function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: string, injectedTexts: LineInjectedText[] | null, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', wrapOnEscapedLineFeeds: boolean): ModelLineProjectionData | null {
359
const lineText = LineInjectedText.applyInjectedText(_lineText, injectedTexts);
360
361
let injectionOptions: InjectedTextOptions[] | null;
362
let injectionOffsets: number[] | null;
363
if (injectedTexts && injectedTexts.length > 0) {
364
injectionOptions = injectedTexts.map(t => t.options);
365
injectionOffsets = injectedTexts.map(text => text.column - 1);
366
} else {
367
injectionOptions = null;
368
injectionOffsets = null;
369
}
370
371
if (firstLineBreakColumn === -1) {
372
if (!injectionOptions) {
373
return null;
374
}
375
// creating a `LineBreakData` with an invalid `breakOffsetsVisibleColumn` is OK
376
// because `breakOffsetsVisibleColumn` will never be used because it contains injected text
377
return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], 0);
378
}
379
380
const len = lineText.length;
381
if (len <= 1) {
382
if (!injectionOptions) {
383
return null;
384
}
385
// creating a `LineBreakData` with an invalid `breakOffsetsVisibleColumn` is OK
386
// because `breakOffsetsVisibleColumn` will never be used because it contains injected text
387
return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], 0);
388
}
389
390
const isKeepAll = (wordBreak === 'keepAll');
391
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
392
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
393
394
const breakingOffsets: number[] = [];
395
const breakingOffsetsVisibleColumn: number[] = [];
396
let breakingOffsetsCount: number = 0;
397
let breakOffset = 0;
398
let breakOffsetVisibleColumn = 0;
399
400
let breakingColumn = firstLineBreakColumn;
401
let prevCharCode = lineText.charCodeAt(0);
402
let prevCharCodeClass = classifier.get(prevCharCode);
403
let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar);
404
405
let startOffset = 1;
406
if (strings.isHighSurrogate(prevCharCode)) {
407
// A surrogate pair must always be considered as a single unit, so it is never to be broken
408
visibleColumn += 1;
409
prevCharCode = lineText.charCodeAt(1);
410
prevCharCodeClass = classifier.get(prevCharCode);
411
startOffset++;
412
}
413
414
for (let i = startOffset; i < len; i++) {
415
const charStartOffset = i;
416
const charCode = lineText.charCodeAt(i);
417
let charCodeClass: CharacterClass;
418
let charWidth: number;
419
420
if (strings.isHighSurrogate(charCode)) {
421
// A surrogate pair must always be considered as a single unit, so it is never to be broken
422
i++;
423
charCodeClass = CharacterClass.NONE;
424
charWidth = 2;
425
} else {
426
charCodeClass = classifier.get(charCode);
427
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
428
}
429
430
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) {
431
breakOffset = charStartOffset;
432
breakOffsetVisibleColumn = visibleColumn;
433
}
434
435
visibleColumn += charWidth;
436
437
// literal \n shall trigger a softwrap
438
if (
439
wrapOnEscapedLineFeeds
440
&& i >= 2
441
&& (i < 3 || lineText.charAt(i - 3) !== '\\')
442
&& lineText.charAt(i - 2) === '\\'
443
&& lineText.charAt(i - 1) === 'n'
444
&& lineText.includes('"')
445
) {
446
visibleColumn += breakingColumn;
447
}
448
449
// check if adding character at `i` will go over the breaking column
450
if (visibleColumn > breakingColumn) {
451
// We need to break at least before character at `i`:
452
453
if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
454
// Cannot break at `breakOffset`, must break at `i`
455
breakOffset = charStartOffset;
456
breakOffsetVisibleColumn = visibleColumn - charWidth;
457
}
458
459
breakingOffsets[breakingOffsetsCount] = breakOffset;
460
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
461
breakingOffsetsCount++;
462
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
463
breakOffset = 0;
464
}
465
466
prevCharCode = charCode;
467
prevCharCodeClass = charCodeClass;
468
}
469
470
if (breakingOffsetsCount === 0 && (!injectedTexts || injectedTexts.length === 0)) {
471
return null;
472
}
473
474
// Add last segment
475
breakingOffsets[breakingOffsetsCount] = len;
476
breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn;
477
478
return new ModelLineProjectionData(injectionOffsets, injectionOptions, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
479
}
480
481
function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number {
482
if (charCode === CharCode.Tab) {
483
return (tabSize - (visibleColumn % tabSize));
484
}
485
if (strings.isFullWidthCharacter(charCode)) {
486
return columnsForFullWidthChar;
487
}
488
if (charCode < 32) {
489
// when using `editor.renderControlCharacters`, the substitutions are often wide
490
return columnsForFullWidthChar;
491
}
492
return 1;
493
}
494
495
function tabCharacterWidth(visibleColumn: number, tabSize: number): number {
496
return (tabSize - (visibleColumn % tabSize));
497
}
498
499
/**
500
* Kinsoku Shori : Don't break after a leading character, like an open bracket
501
* Kinsoku Shori : Don't break before a trailing character, like a period
502
*/
503
function canBreak(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass, isKeepAll: boolean): boolean {
504
return (
505
charCode !== CharCode.Space
506
&& (
507
(prevCharCodeClass === CharacterClass.BREAK_AFTER && charCodeClass !== CharacterClass.BREAK_AFTER) // break at the end of multiple BREAK_AFTER
508
|| (prevCharCodeClass !== CharacterClass.BREAK_BEFORE && charCodeClass === CharacterClass.BREAK_BEFORE) // break at the start of multiple BREAK_BEFORE
509
|| (!isKeepAll && prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
510
|| (!isKeepAll && charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
511
)
512
);
513
}
514
515
function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): number {
516
let wrappedTextIndentLength = 0;
517
if (wrappingIndent !== WrappingIndent.None) {
518
const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
519
if (firstNonWhitespaceIndex !== -1) {
520
// Track existing indent
521
522
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
523
const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1);
524
wrappedTextIndentLength += charWidth;
525
}
526
527
// Increase indent of continuation lines, if desired
528
const numberOfAdditionalTabs = (wrappingIndent === WrappingIndent.DeepIndent ? 2 : wrappingIndent === WrappingIndent.Indent ? 1 : 0);
529
for (let i = 0; i < numberOfAdditionalTabs; i++) {
530
const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize);
531
wrappedTextIndentLength += charWidth;
532
}
533
534
// Force sticking to beginning of line if no character would fit except for the indentation
535
if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakColumn) {
536
wrappedTextIndentLength = 0;
537
}
538
}
539
}
540
return wrappedTextIndentLength;
541
}
542
543