Skip to content

Commit db744ee

Browse files
committed
Support wordBreak also in the advanced wrapping strategy and react to changing the option at runtime
1 parent a52fe3b commit db744ee

File tree

10 files changed

+114
-104
lines changed

10 files changed

+114
-104
lines changed

src/vs/editor/browser/view/domLineBreaksComputer.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory
2424
constructor() {
2525
}
2626

27-
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
27+
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ILineBreaksComputer {
2828
const requests: string[] = [];
2929
const injectedTexts: (LineInjectedText[] | null)[] = [];
3030
return {
@@ -33,13 +33,13 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory
3333
injectedTexts.push(injectedText);
3434
},
3535
finalize: () => {
36-
return createLineBreaks(requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, injectedTexts);
36+
return createLineBreaks(requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts);
3737
}
3838
};
3939
}
4040
}
4141

42-
function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, injectedTextsPerLine: (LineInjectedText[] | null)[]): (ModelLineProjectionData | null)[] {
42+
function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', injectedTextsPerLine: (LineInjectedText[] | null)[]): (ModelLineProjectionData | null)[] {
4343
function createEmptyLineBreakWithPossiblyInjectedText(requestIdx: number): ModelLineProjectionData | null {
4444
const injectedTexts = injectedTextsPerLine[requestIdx];
4545
if (injectedTexts) {
@@ -129,7 +129,15 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe
129129

130130
containerDomNode.style.position = 'absolute';
131131
containerDomNode.style.top = '10000';
132-
containerDomNode.style.wordWrap = 'break-word';
132+
if (wordBreak === 'keepAll') {
133+
// word-break: keep-all; overflow-wrap: anywhere
134+
containerDomNode.style.wordBreak = 'keep-all';
135+
containerDomNode.style.overflowWrap = 'anywhere';
136+
} else {
137+
// overflow-wrap: break-word
138+
containerDomNode.style.wordBreak = 'inherit';
139+
containerDomNode.style.overflowWrap = 'break-word';
140+
}
133141
document.body.appendChild(containerDomNode);
134142

135143
const range = document.createRange();

src/vs/editor/common/config/editorOptions.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4775,14 +4775,14 @@ export const enum EditorOption {
47754775
unusualLineTerminators,
47764776
useShadowDOM,
47774777
useTabStops,
4778+
wordBreak,
47784779
wordSeparators,
47794780
wordWrap,
47804781
wordWrapBreakAfterCharacters,
47814782
wordWrapBreakBeforeCharacters,
47824783
wordWrapColumn,
47834784
wordWrapOverride1,
47844785
wordWrapOverride2,
4785-
wordBreak,
47864786
wrappingIndent,
47874787
wrappingStrategy,
47884788
showDeprecated,
@@ -5388,6 +5388,18 @@ export const EditorOptions = {
53885388
EditorOption.useTabStops, 'useTabStops', true,
53895389
{ description: nls.localize('useTabStops', "Inserting and deleting whitespace follows tab stops.") }
53905390
)),
5391+
wordBreak: register(new EditorStringEnumOption(
5392+
EditorOption.wordBreak, 'wordBreak',
5393+
'normal' as 'normal' | 'keepAll',
5394+
['normal', 'keepAll'] as const,
5395+
{
5396+
markdownEnumDescriptions: [
5397+
nls.localize('wordBreak.normal', "Use the default line break rule."),
5398+
nls.localize('wordBreak.keepAll', "Word breaks should not be used for Chinese/Japanese/Korean (CJK) text. Non-CJK text behavior is the same as for normal."),
5399+
],
5400+
description: nls.localize('wordBreak', "Controls the word break rules used for Chinese/Japanese/Korean (CJK) text.")
5401+
}
5402+
)),
53915403
wordSeparators: register(new EditorStringOption(
53925404
EditorOption.wordSeparators, 'wordSeparators', USUAL_WORD_SEPARATORS,
53935405
{ description: nls.localize('wordSeparators', "Characters that will be used as word separators when doing word related navigations or operations.") }
@@ -5433,23 +5445,6 @@ export const EditorOptions = {
54335445
// allow-any-unicode-next-line
54345446
'([{‘“〈《「『【〔([{「£¥$£¥++'
54355447
)),
5436-
wordBreak: register(new EditorStringEnumOption(
5437-
EditorOption.wordBreak, 'wordBreak',
5438-
'normal' as 'normal' | 'keepAll',
5439-
['normal', 'keepAll'] as const,
5440-
{
5441-
markdownEnumDescriptions: [
5442-
nls.localize('wordBreak.normal', "Use the default line break rule."),
5443-
nls.localize('wordBreak.keepAll', "Word breaks should not be used for Chinese/Japanese/Korean (CJK) text. Non-CJK text behavior is the same as for normal."),
5444-
],
5445-
description: nls.localize({
5446-
key: 'wordBreak',
5447-
comment: [
5448-
''
5449-
]
5450-
}, "Sets whether line breaks appear wherever the text would otherwise overflow its content box.")
5451-
}
5452-
)),
54535448
wordWrapColumn: register(new EditorIntOption(
54545449
EditorOption.wordWrapColumn, 'wordWrapColumn',
54555450
80, 1, Constants.MAX_SAFE_SMALL_INTEGER,

src/vs/editor/common/modelLineProjectionData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ export class OutputPosition {
329329
}
330330

331331
export interface ILineBreaksComputerFactory {
332-
createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer;
332+
createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ILineBreaksComputer;
333333
}
334334

335335
export interface ILineBreaksComputer {

src/vs/editor/common/standalone/standaloneEnums.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,14 +291,14 @@ export enum EditorOption {
291291
unusualLineTerminators = 116,
292292
useShadowDOM = 117,
293293
useTabStops = 118,
294-
wordSeparators = 119,
295-
wordWrap = 120,
296-
wordWrapBreakAfterCharacters = 121,
297-
wordWrapBreakBeforeCharacters = 122,
298-
wordWrapColumn = 123,
299-
wordWrapOverride1 = 124,
300-
wordWrapOverride2 = 125,
301-
wordBreak = 126,
294+
wordBreak = 119,
295+
wordSeparators = 120,
296+
wordWrap = 121,
297+
wordWrapBreakAfterCharacters = 122,
298+
wordWrapBreakBeforeCharacters = 123,
299+
wordWrapColumn = 124,
300+
wordWrapOverride1 = 125,
301+
wordWrapOverride2 = 126,
302302
wrappingIndent = 127,
303303
wrappingStrategy = 128,
304304
showDeprecated = 129,

src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { CharCode } from 'vs/base/common/charCode';
77
import * as strings from 'vs/base/common/strings';
8-
import { WrappingIndent, IComputedEditorOptions, EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions';
8+
import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
99
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
1010
import { FontInfo } from 'vs/editor/common/config/fontInfo';
1111
import { LineInjectedText } from 'vs/editor/common/textModelEvents';
@@ -16,18 +16,17 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa
1616
public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory {
1717
return new MonospaceLineBreaksComputerFactory(
1818
options.get(EditorOption.wordWrapBreakBeforeCharacters),
19-
options.get(EditorOption.wordWrapBreakAfterCharacters),
20-
options.get(EditorOption.wordBreak)
19+
options.get(EditorOption.wordWrapBreakAfterCharacters)
2120
);
2221
}
2322

2423
private readonly classifier: WrappingCharacterClassifier;
2524

26-
constructor(breakBeforeChars: string, breakAfterChars: string, wordBreakMode: IEditorOptions['wordBreak']) {
27-
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars, wordBreakMode);
25+
constructor(breakBeforeChars: string, breakAfterChars: string) {
26+
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars);
2827
}
2928

30-
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
29+
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ILineBreaksComputer {
3130
const requests: string[] = [];
3231
const injectedTexts: (LineInjectedText[] | null)[] = [];
3332
const previousBreakingData: (ModelLineProjectionData | null)[] = [];
@@ -44,9 +43,9 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa
4443
const injectedText = injectedTexts[i];
4544
const previousLineBreakData = previousBreakingData[i];
4645
if (previousLineBreakData && !previousLineBreakData.injectionOptions && !injectedText) {
47-
result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
46+
result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak);
4847
} else {
49-
result[i] = createLineBreaks(this.classifier, requests[i], injectedText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
48+
result[i] = createLineBreaks(this.classifier, requests[i], injectedText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak);
5049
}
5150
}
5251
arrPool1.length = 0;
@@ -66,8 +65,7 @@ const enum CharacterClass {
6665

6766
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
6867

69-
private readonly isKeepAll: boolean;
70-
constructor(BREAK_BEFORE: string, BREAK_AFTER: string, wordBreakMode: IEditorOptions['wordBreak']) {
68+
constructor(BREAK_BEFORE: string, BREAK_AFTER: string) {
7169
super(CharacterClass.NONE);
7270

7371
for (let i = 0; i < BREAK_BEFORE.length; i++) {
@@ -77,7 +75,6 @@ class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
7775
for (let i = 0; i < BREAK_AFTER.length; i++) {
7876
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
7977
}
80-
this.isKeepAll = wordBreakMode === 'keepAll';
8178
}
8279

8380
public override get(charCode: number): CharacterClass {
@@ -88,13 +85,10 @@ class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
8885
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
8986
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
9087
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
91-
// Except for the case where wordBreak is set to keepAll
9288
if (
93-
!this.isKeepAll && (
94-
(charCode >= 0x3040 && charCode <= 0x30FF)
95-
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
96-
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
97-
)
89+
(charCode >= 0x3040 && charCode <= 0x30FF)
90+
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
91+
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
9892
) {
9993
return CharacterClass.BREAK_IDEOGRAPHIC;
10094
}
@@ -107,7 +101,7 @@ class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
107101
let arrPool1: number[] = [];
108102
let arrPool2: number[] = [];
109103

110-
function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: ModelLineProjectionData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ModelLineProjectionData | null {
104+
function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: ModelLineProjectionData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ModelLineProjectionData | null {
111105
if (firstLineBreakColumn === -1) {
112106
return null;
113107
}
@@ -117,6 +111,8 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
117111
return null;
118112
}
119113

114+
const isKeepAll = (wordBreak === 'keepAll');
115+
120116
const prevBreakingOffsets = previousBreakingData.breakOffsets;
121117
const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakOffsetsVisibleColumn;
122118

@@ -182,7 +178,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
182178
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
183179
}
184180

185-
if (charStartOffset > lastBreakingOffset && canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
181+
if (charStartOffset > lastBreakingOffset && canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) {
186182
breakOffset = charStartOffset;
187183
breakOffsetVisibleColumn = visibleColumn;
188184
}
@@ -266,7 +262,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
266262
break;
267263
}
268264

269-
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
265+
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) {
270266
breakOffset = charStartOffset;
271267
breakOffsetVisibleColumn = visibleColumn;
272268
break;
@@ -359,7 +355,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
359355
return previousBreakingData;
360356
}
361357

362-
function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: string, injectedTexts: LineInjectedText[] | null, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ModelLineProjectionData | null {
358+
function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: string, injectedTexts: LineInjectedText[] | null, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ModelLineProjectionData | null {
363359
const lineText = LineInjectedText.applyInjectedText(_lineText, injectedTexts);
364360

365361
let injectionOptions: InjectedTextOptions[] | null;
@@ -391,6 +387,7 @@ function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: st
391387
return new ModelLineProjectionData(injectionOffsets, injectionOptions, [lineText.length], [], 0);
392388
}
393389

390+
const isKeepAll = (wordBreak === 'keepAll');
394391
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
395392
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
396393

@@ -430,7 +427,7 @@ function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: st
430427
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
431428
}
432429

433-
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
430+
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) {
434431
breakOffset = charStartOffset;
435432
breakOffsetVisibleColumn = visibleColumn;
436433
}
@@ -491,14 +488,14 @@ function tabCharacterWidth(visibleColumn: number, tabSize: number): number {
491488
* Kinsoku Shori : Don't break after a leading character, like an open bracket
492489
* Kinsoku Shori : Don't break before a trailing character, like a period
493490
*/
494-
function canBreak(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass): boolean {
491+
function canBreak(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass, isKeepAll: boolean): boolean {
495492
return (
496493
charCode !== CharCode.Space
497494
&& (
498495
(prevCharCodeClass === CharacterClass.BREAK_AFTER && charCodeClass !== CharacterClass.BREAK_AFTER) // break at the end of multiple BREAK_AFTER
499496
|| (prevCharCodeClass !== CharacterClass.BREAK_BEFORE && charCodeClass === CharacterClass.BREAK_BEFORE) // break at the start of multiple BREAK_BEFORE
500-
|| (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
501-
|| (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
497+
|| (!isKeepAll && prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
498+
|| (!isKeepAll && charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
502499
)
503500
);
504501
}

src/vs/editor/common/viewModel/viewModelImpl.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export class ViewModel extends Disposable implements IViewModel {
9393
const wrappingStrategy = options.get(EditorOption.wrappingStrategy);
9494
const wrappingInfo = options.get(EditorOption.wrappingInfo);
9595
const wrappingIndent = options.get(EditorOption.wrappingIndent);
96+
const wordBreak = options.get(EditorOption.wordBreak);
9697

9798
this._lines = new ViewModelLinesFromProjectedModel(
9899
this._editorId,
@@ -103,7 +104,8 @@ export class ViewModel extends Disposable implements IViewModel {
103104
this.model.getOptions().tabSize,
104105
wrappingStrategy,
105106
wrappingInfo.wrappingColumn,
106-
wrappingIndent
107+
wrappingIndent,
108+
wordBreak
107109
);
108110
}
109111

@@ -230,8 +232,9 @@ export class ViewModel extends Disposable implements IViewModel {
230232
const wrappingStrategy = options.get(EditorOption.wrappingStrategy);
231233
const wrappingInfo = options.get(EditorOption.wrappingInfo);
232234
const wrappingIndent = options.get(EditorOption.wrappingIndent);
235+
const wordBreak = options.get(EditorOption.wordBreak);
233236

234-
if (this._lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent)) {
237+
if (this._lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent, wordBreak)) {
235238
eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());
236239
eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());
237240
eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));

0 commit comments

Comments
 (0)