Skip to content

Commit 7c711c1

Browse files
committed
Add wordBreak editorOption and use it to lineBreakComputer function
1 parent e4ed3e0 commit 7c711c1

File tree

6 files changed

+80
-30
lines changed

6 files changed

+80
-30
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,12 @@ export interface IEditorOptions {
311311
* Configure word wrapping characters. A break will be introduced after these characters.
312312
*/
313313
wordWrapBreakAfterCharacters?: string;
314+
/**
315+
* Sets whether line breaks appear wherever the text would otherwise overflow its content box.
316+
* When wordBreak = 'normal', Use the default line break rule.
317+
* When wordBreak = 'keepAll', Word breaks should not be used for Chinese/Japanese/Korean (CJK) text. Non-CJK text behavior is the same as for normal.
318+
*/
319+
wordBreak?: 'normal' | 'keepAll';
314320
/**
315321
* Performance guard: Stop rendering a line after x characters.
316322
* Defaults to 10000.
@@ -4595,6 +4601,7 @@ export const enum EditorOption {
45954601
wordWrapColumn,
45964602
wordWrapOverride1,
45974603
wordWrapOverride2,
4604+
wordBreak,
45984605
wrappingIndent,
45994606
wrappingStrategy,
46004607
showDeprecated,
@@ -5249,6 +5256,23 @@ export const EditorOptions = {
52495256
// allow-any-unicode-next-line
52505257
'([{‘“〈《「『【〔([{「£¥$£¥++'
52515258
)),
5259+
wordBreak: register(new EditorStringEnumOption(
5260+
EditorOption.wordBreak, 'wordBreak',
5261+
'normal' as 'normal' | 'keepAll',
5262+
['normal', 'keepAll'] as const,
5263+
{
5264+
markdownEnumDescriptions: [
5265+
nls.localize('wordBreak.normal', "Use the default line break rule."),
5266+
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."),
5267+
],
5268+
description: nls.localize({
5269+
key: 'wordBreak',
5270+
comment: [
5271+
''
5272+
]
5273+
}, "Sets whether line breaks appear wherever the text would otherwise overflow its content box.")
5274+
}
5275+
)),
52525276
wordWrapColumn: register(new EditorIntOption(
52535277
EditorOption.wordWrapColumn, 'wordWrapColumn',
52545278
80, 1, Constants.MAX_SAFE_SMALL_INTEGER,

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ export enum EditorOption {
306306
pixelRatio = 131,
307307
tabFocusMode = 132,
308308
layoutInfo = 133,
309-
wrappingInfo = 134
309+
wrappingInfo = 134,
310+
wordBreak = 135,
310311
}
311312

312313
/**
@@ -897,4 +898,4 @@ export enum WrappingIndent {
897898
* DeepIndent => wrapped lines get +2 indentation toward the parent.
898899
*/
899900
DeepIndent = 3
900-
}
901+
}

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

Lines changed: 14 additions & 8 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 } from 'vs/editor/common/config/editorOptions';
8+
import { WrappingIndent, IComputedEditorOptions, EditorOption, IEditorOptions } 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,14 +16,15 @@ 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)
19+
options.get(EditorOption.wordWrapBreakAfterCharacters),
20+
options.get(EditorOption.wordBreak)
2021
);
2122
}
2223

2324
private readonly classifier: WrappingCharacterClassifier;
2425

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

2930
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
@@ -65,7 +66,8 @@ const enum CharacterClass {
6566

6667
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
6768

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

7173
for (let i = 0; i < BREAK_BEFORE.length; i++) {
@@ -75,6 +77,7 @@ class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
7577
for (let i = 0; i < BREAK_AFTER.length; i++) {
7678
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
7779
}
80+
this.isKeepAll = wordBreakMode === 'keepAll';
7881
}
7982

8083
public override get(charCode: number): CharacterClass {
@@ -85,10 +88,13 @@ class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
8588
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
8689
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
8790
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
91+
// Except for the case where wordBreak is set to keepAll
8892
if (
89-
(charCode >= 0x3040 && charCode <= 0x30FF)
90-
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
91-
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
93+
!this.isKeepAll && (
94+
(charCode >= 0x3040 && charCode <= 0x30FF)
95+
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
96+
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
97+
)
9298
) {
9399
return CharacterClass.BREAK_IDEOGRAPHIC;
94100
}

src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
9696
const wordWrapBreakAfterCharacters = config.options.get(EditorOption.wordWrapBreakAfterCharacters);
9797
const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters);
9898
const wrappingIndent = config.options.get(EditorOption.wrappingIndent);
99-
100-
const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters);
99+
const wordBreak = config.options.get(EditorOption.wordBreak);
100+
const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters, wordBreak);
101101

102102
const model = createTextModel([
103103
'int main() {',
@@ -949,8 +949,9 @@ suite('SplitLinesCollection', () => {
949949
const wordWrapBreakAfterCharacters = configuration.options.get(EditorOption.wordWrapBreakAfterCharacters);
950950
const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters);
951951
const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent);
952+
const wordBreak = configuration.options.get(EditorOption.wordBreak);
952953

953-
const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters);
954+
const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters, wordBreak);
954955

955956
const linesCollection = new ViewModelLinesFromProjectedModel(
956957
1,

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

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ function assertLineBreaks(factory: ILineBreaksComputerFactory, tabSize: number,
7777

7878
return lineBreakData;
7979
}
80-
80+
const wordBreakEditorOptionDefaultValue = EditorOptions.wordBreak.defaultValue;
8181
suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
8282
test('MonospaceLineBreaksComputer', () => {
8383

84-
const factory = new MonospaceLineBreaksComputerFactory('(', '\t).');
84+
const factory = new MonospaceLineBreaksComputerFactory('(', '\t).', wordBreakEditorOptionDefaultValue);
8585

8686
// Empty string
8787
assertLineBreaks(factory, 4, 5, '');
@@ -160,7 +160,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
160160

161161
test('MonospaceLineBreaksComputer incremental 1', () => {
162162

163-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
163+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
164164

165165
assertIncrementalLineBreaks(
166166
factory, 'just some text and more', 4,
@@ -217,7 +217,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
217217
});
218218

219219
test('issue #95686: CRITICAL: loop forever on the monospaceLineBreaksComputer', () => {
220-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
220+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
221221
assertIncrementalLineBreaks(
222222
factory,
223223
' <tr dmx-class:table-danger="(alt <= 50)" dmx-class:table-warning="(alt <= 200)" dmx-class:table-primary="(alt <= 400)" dmx-class:table-info="(alt <= 800)" dmx-class:table-success="(alt >= 400)">',
@@ -229,7 +229,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
229229
});
230230

231231
test('issue #110392: Occasional crash when resize with panel on the right', () => {
232-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
232+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
233233
assertIncrementalLineBreaks(
234234
factory,
235235
'你好 **hello** **hello** **hello-world** hey there!',
@@ -242,7 +242,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
242242
});
243243

244244
test('MonospaceLineBreaksComputer - CJK and Kinsoku Shori', () => {
245-
const factory = new MonospaceLineBreaksComputerFactory('(', '\t)');
245+
const factory = new MonospaceLineBreaksComputerFactory('(', '\t)', wordBreakEditorOptionDefaultValue);
246246
assertLineBreaks(factory, 4, 5, 'aa \u5b89|\u5b89');
247247
assertLineBreaks(factory, 4, 5, '\u3042 \u5b89|\u5b89');
248248
assertLineBreaks(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89');
@@ -252,54 +252,64 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
252252
});
253253

254254
test('MonospaceLineBreaksComputer - WrappingIndent.Same', () => {
255-
const factory = new MonospaceLineBreaksComputerFactory('', '\t ');
255+
const factory = new MonospaceLineBreaksComputerFactory('', '\t ', wordBreakEditorOptionDefaultValue);
256256
assertLineBreaks(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same);
257257
});
258258

259259
test('issue #16332: Scroll bar overlaying on top of text', () => {
260-
const factory = new MonospaceLineBreaksComputerFactory('', '\t ');
260+
const factory = new MonospaceLineBreaksComputerFactory('', '\t ', wordBreakEditorOptionDefaultValue);
261261
assertLineBreaks(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent);
262262
});
263263

264264
test('issue #35162: wrappingIndent not consistently working', () => {
265-
const factory = new MonospaceLineBreaksComputerFactory('', '\t ');
265+
const factory = new MonospaceLineBreaksComputerFactory('', '\t ', wordBreakEditorOptionDefaultValue);
266266
const mapper = assertLineBreaks(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent);
267267
assert.strictEqual(mapper!.wrappedTextIndentLength, ' '.length);
268268
});
269269

270270
test('issue #75494: surrogate pairs', () => {
271-
const factory = new MonospaceLineBreaksComputerFactory('\t', ' ');
271+
const factory = new MonospaceLineBreaksComputerFactory('\t', ' ', wordBreakEditorOptionDefaultValue);
272272
assertLineBreaks(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬', WrappingIndent.Same);
273273
});
274274

275275
test('issue #75494: surrogate pairs overrun 1', () => {
276-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
276+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
277277
assertLineBreaks(factory, 4, 4, '🐇👬|&|🌞🌖', WrappingIndent.Same);
278278
});
279279

280280
test('issue #75494: surrogate pairs overrun 2', () => {
281-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
281+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
282282
assertLineBreaks(factory, 4, 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', WrappingIndent.Same);
283283
});
284284

285285
test('MonospaceLineBreaksComputer - WrappingIndent.DeepIndent', () => {
286-
const factory = new MonospaceLineBreaksComputerFactory('', '\t ');
286+
const factory = new MonospaceLineBreaksComputerFactory('', '\t ', wordBreakEditorOptionDefaultValue);
287287
const mapper = assertLineBreaks(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent);
288288
assert.strictEqual(mapper!.wrappedTextIndentLength, ' '.length);
289289
});
290290

291291
test('issue #33366: Word wrap algorithm behaves differently around punctuation', () => {
292-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
292+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
293293
assertLineBreaks(factory, 4, 23, 'this is a line of |text, text that sits |on a line', WrappingIndent.Same);
294294
});
295295

296296
test('issue #152773: Word wrap algorithm behaves differently with bracket followed by comma', () => {
297-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
297+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
298298
assertLineBreaks(factory, 4, 24, 'this is a line of |(text), text that sits |on a line', WrappingIndent.Same);
299299
});
300300

301301
test('issue #112382: Word wrap doesn\'t work well with control characters', () => {
302-
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
302+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
303303
assertLineBreaks(factory, 4, 6, '\x06\x06\x06|\x06\x06\x06', WrappingIndent.Same);
304304
});
305+
306+
test('Word break work well with Chinese/Japanese/Korean (CJK) text when setting normal', () => {
307+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, wordBreakEditorOptionDefaultValue);
308+
assertLineBreaks(factory, 4, 5, '你好|1111', WrappingIndent.Same);
309+
});
310+
311+
test('Word break work well with Chinese/Japanese/Korean (CJK) text when setting keepAll', () => {
312+
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue, 'keepAll');
313+
assertLineBreaks(factory, 4, 8, '你好1111', WrappingIndent.Same);
314+
});
305315
});

src/vs/monaco.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3088,6 +3088,12 @@ declare namespace monaco.editor {
30883088
* Configure word wrapping characters. A break will be introduced after these characters.
30893089
*/
30903090
wordWrapBreakAfterCharacters?: string;
3091+
/**
3092+
* Sets whether line breaks appear wherever the text would otherwise overflow its content box.
3093+
* When wordBreak = 'normal', Use the default line break rule.
3094+
* When wordBreak = 'keepAll', Word breaks should not be used for Chinese/Japanese/Korean (CJK) text. Non-CJK text behavior is the same as for normal.
3095+
*/
3096+
wordBreak?: 'normal' | 'keepAll';
30913097
/**
30923098
* Performance guard: Stop rendering a line after x characters.
30933099
* Defaults to 10000.
@@ -4465,7 +4471,8 @@ declare namespace monaco.editor {
44654471
pixelRatio = 131,
44664472
tabFocusMode = 132,
44674473
layoutInfo = 133,
4468-
wrappingInfo = 134
4474+
wrappingInfo = 134,
4475+
wordBreak = 135
44694476
}
44704477

44714478
export const EditorOptions: {
@@ -4594,6 +4601,7 @@ declare namespace monaco.editor {
45944601
wordWrap: IEditorOption<EditorOption.wordWrap, 'on' | 'off' | 'wordWrapColumn' | 'bounded'>;
45954602
wordWrapBreakAfterCharacters: IEditorOption<EditorOption.wordWrapBreakAfterCharacters, string>;
45964603
wordWrapBreakBeforeCharacters: IEditorOption<EditorOption.wordWrapBreakBeforeCharacters, string>;
4604+
wordBreak: IEditorOption<EditorOption.wordBreak, 'normal' | 'keepAll'>;
45974605
wordWrapColumn: IEditorOption<EditorOption.wordWrapColumn, number>;
45984606
wordWrapOverride1: IEditorOption<EditorOption.wordWrapOverride1, 'on' | 'off' | 'inherit'>;
45994607
wordWrapOverride2: IEditorOption<EditorOption.wordWrapOverride2, 'on' | 'off' | 'inherit'>;

0 commit comments

Comments
 (0)