Skip to content

Commit af7765c

Browse files
committed
Add 'trailing' option to render whitespace
1 parent 6d913c4 commit af7765c

5 files changed

Lines changed: 116 additions & 12 deletions

File tree

src/vs/editor/browser/viewParts/lines/viewLine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class DomReadingContext {
7272

7373
export class ViewLineOptions {
7474
public readonly themeType: ThemeType;
75-
public readonly renderWhitespace: 'none' | 'boundary' | 'selection' | 'all';
75+
public readonly renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all';
7676
public readonly renderControlCharacters: boolean;
7777
public readonly spaceWidth: number;
7878
public readonly middotWidth: number;

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ export interface IEditorOptions {
529529
* Enable rendering of whitespace.
530530
* Defaults to none.
531531
*/
532-
renderWhitespace?: 'none' | 'boundary' | 'selection' | 'all';
532+
renderWhitespace?: 'none' | 'boundary' | 'selection' | 'trailing' | 'all';
533533
/**
534534
* Enable rendering of control characters.
535535
* Defaults to false.
@@ -4037,13 +4037,14 @@ export const EditorOptions = {
40374037
)),
40384038
renderWhitespace: register(new EditorStringEnumOption(
40394039
EditorOption.renderWhitespace, 'renderWhitespace',
4040-
'selection' as 'selection' | 'none' | 'boundary' | 'all',
4041-
['none', 'boundary', 'selection', 'all'] as const,
4040+
'selection' as 'selection' | 'none' | 'boundary' | 'trailing' | 'all',
4041+
['none', 'boundary', 'selection', 'trailing', 'all'] as const,
40424042
{
40434043
enumDescriptions: [
40444044
'',
40454045
nls.localize('renderWhitespace.boundary', "Render whitespace characters except for single spaces between words."),
40464046
nls.localize('renderWhitespace.selection', "Render whitespace characters only on selected text."),
4047+
nls.localize('renderWhitespace.trailing', "Render only trailing whitespace characters"),
40474048
''
40484049
],
40494050
description: nls.localize('renderWhitespace', "Controls how the editor should render whitespace characters.")

src/vs/editor/common/viewLayout/viewLineRenderer.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const enum RenderWhitespace {
1414
None = 0,
1515
Boundary = 1,
1616
Selection = 2,
17-
All = 3
17+
Trailing = 3,
18+
All = 4
1819
}
1920

2021
export const enum LinePartMetadata {
@@ -113,7 +114,7 @@ export class RenderLineInput {
113114
middotWidth: number,
114115
wsmiddotWidth: number,
115116
stopRenderingLineAfter: number,
116-
renderWhitespace: 'none' | 'boundary' | 'selection' | 'all',
117+
renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all',
117118
renderControlCharacters: boolean,
118119
fontLigatures: boolean,
119120
selectionsOnLine: LineRange[] | null
@@ -138,7 +139,9 @@ export class RenderLineInput {
138139
? RenderWhitespace.Boundary
139140
: renderWhitespace === 'selection'
140141
? RenderWhitespace.Selection
141-
: RenderWhitespace.None
142+
: renderWhitespace === 'trailing'
143+
? RenderWhitespace.Trailing
144+
: RenderWhitespace.None
142145
);
143146
this.renderControlCharacters = renderControlCharacters;
144147
this.fontLigatures = fontLigatures;
@@ -435,7 +438,11 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
435438
}
436439

437440
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
438-
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) {
441+
if (input.renderWhitespace === RenderWhitespace.All ||
442+
input.renderWhitespace === RenderWhitespace.Boundary ||
443+
(input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine) ||
444+
input.renderWhitespace === RenderWhitespace.Trailing) {
445+
439446
tokens = _applyRenderWhitespace(input, lineContent, len, tokens);
440447
}
441448
let containsForeignElements = ForeignElementType.None;
@@ -592,6 +599,7 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len
592599
const useMonospaceOptimizations = input.useMonospaceOptimizations;
593600
const selections = input.selectionsOnLine;
594601
const onlyBoundary = (input.renderWhitespace === RenderWhitespace.Boundary);
602+
const onlyTrailing = (input.renderWhitespace === RenderWhitespace.Trailing);
595603
const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth);
596604

597605
let result: LinePart[] = [], resultLen = 0;
@@ -600,10 +608,11 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len
600608
let tokenEndIndex = tokens[tokenIndex].endIndex;
601609
const tokensLength = tokens.length;
602610

611+
let lineIsEmptyOrWhitespace = false;
603612
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
604613
let lastNonWhitespaceIndex: number;
605614
if (firstNonWhitespaceIndex === -1) {
606-
// The entire line is whitespace
615+
lineIsEmptyOrWhitespace = true;
607616
firstNonWhitespaceIndex = len;
608617
lastNonWhitespaceIndex = len;
609618
} else {
@@ -651,6 +660,11 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len
651660
isInWhitespace = !!currentSelection && currentSelection.startOffset <= charIndex && currentSelection.endOffset > charIndex;
652661
}
653662

663+
// If rendering only trailing whitespace, check that the charIndex points to trailing whitespace.
664+
if (isInWhitespace && onlyTrailing) {
665+
isInWhitespace = lineIsEmptyOrWhitespace || charIndex > lastNonWhitespaceIndex;
666+
}
667+
654668
if (wasInWhitespace) {
655669
// was in whitespace token
656670
if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {

src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ suite('viewLineRenderer.renderLine', () => {
869869

870870
suite('viewLineRenderer.renderLine 2', () => {
871871

872-
function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'all', selections: LineRange[] | null, expected: string): void {
872+
function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all', selections: LineRange[] | null, expected: string): void {
873873
let actual = renderViewLine(new RenderLineInput(
874874
fontIsMonospace,
875875
true,
@@ -1355,6 +1355,95 @@ suite('viewLineRenderer.renderLine 2', () => {
13551355
);
13561356
});
13571357

1358+
test('createLineParts render whitespace for trailing with leading, inner, and without trailing whitespace', () => {
1359+
testCreateLineParts(
1360+
false,
1361+
' Hello world!',
1362+
[
1363+
createPart(4, 0),
1364+
createPart(6, 1),
1365+
createPart(14, 2)
1366+
],
1367+
0,
1368+
'trailing',
1369+
null,
1370+
[
1371+
'<span>',
1372+
'<span class="mtk0">\u00a0Hel</span>',
1373+
'<span class="mtk1">lo</span>',
1374+
'<span class="mtk2">\u00a0world!</span>',
1375+
'</span>',
1376+
].join('')
1377+
);
1378+
});
1379+
1380+
test('createLineParts render whitespace for trailing with leading, inner, and trailing whitespace', () => {
1381+
testCreateLineParts(
1382+
false,
1383+
' Hello world! \t',
1384+
[
1385+
createPart(4, 0),
1386+
createPart(6, 1),
1387+
createPart(15, 2)
1388+
],
1389+
0,
1390+
'trailing',
1391+
null,
1392+
[
1393+
'<span>',
1394+
'<span class="mtk0">\u00a0Hel</span>',
1395+
'<span class="mtk1">lo</span>',
1396+
'<span class="mtk2">\u00a0world!</span>',
1397+
'<span class="mtkz" style="width:30px">\u00b7\u2192\u00a0</span>',
1398+
'</span>',
1399+
].join('')
1400+
);
1401+
});
1402+
1403+
test('createLineParts render whitespace for trailing with 8 leading and 8 trailing whitespaces', () => {
1404+
testCreateLineParts(
1405+
false,
1406+
' Hello world! ',
1407+
[
1408+
createPart(8, 1),
1409+
createPart(10, 2),
1410+
createPart(28, 3)
1411+
],
1412+
0,
1413+
'trailing',
1414+
null,
1415+
[
1416+
'<span>',
1417+
'<span class="mtk1">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0</span>',
1418+
'<span class="mtk2">He</span>',
1419+
'<span class="mtk3">llo\u00a0world!</span>',
1420+
'<span class="mtkz" style="width:40px">\u00b7\u00b7\u00b7\u00b7</span>',
1421+
'<span class="mtkz" style="width:40px">\u00b7\u00b7\u00b7\u00b7</span>',
1422+
'</span>',
1423+
].join('')
1424+
);
1425+
});
1426+
1427+
test('createLineParts render whitespace for trailing with line containing only whitespaces', () => {
1428+
testCreateLineParts(
1429+
false,
1430+
' \t ',
1431+
[
1432+
createPart(2, 0),
1433+
createPart(3, 1),
1434+
],
1435+
0,
1436+
'trailing',
1437+
null,
1438+
[
1439+
'<span>',
1440+
'<span class="mtkz" style="width:40px">\u00b7\u2192\u00a0\u00a0</span>',
1441+
'<span class="mtkz" style="width:10px">\u00b7</span>',
1442+
'</span>',
1443+
].join('')
1444+
);
1445+
});
1446+
13581447
test('createLineParts can handle unsorted inline decorations', () => {
13591448
let actual = renderViewLine(new RenderLineInput(
13601449
false,

src/vs/monaco.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3068,7 +3068,7 @@ declare namespace monaco.editor {
30683068
* Enable rendering of whitespace.
30693069
* Defaults to none.
30703070
*/
3071-
renderWhitespace?: 'none' | 'boundary' | 'selection' | 'all';
3071+
renderWhitespace?: 'none' | 'boundary' | 'selection' | 'trailing' | 'all';
30723072
/**
30733073
* Enable rendering of control characters.
30743074
* Defaults to false.
@@ -4048,7 +4048,7 @@ declare namespace monaco.editor {
40484048
renderLineHighlight: IEditorOption<EditorOption.renderLineHighlight, 'all' | 'line' | 'none' | 'gutter'>;
40494049
renderLineHighlightOnlyWhenFocus: IEditorOption<EditorOption.renderLineHighlightOnlyWhenFocus, boolean>;
40504050
renderValidationDecorations: IEditorOption<EditorOption.renderValidationDecorations, 'on' | 'off' | 'editable'>;
4051-
renderWhitespace: IEditorOption<EditorOption.renderWhitespace, 'all' | 'none' | 'boundary' | 'selection'>;
4051+
renderWhitespace: IEditorOption<EditorOption.renderWhitespace, 'all' | 'none' | 'boundary' | 'selection' | 'trailing'>;
40524052
revealHorizontalRightPadding: IEditorOption<EditorOption.revealHorizontalRightPadding, number>;
40534053
roundedSelection: IEditorOption<EditorOption.roundedSelection, boolean>;
40544054
rulers: IEditorOption<EditorOption.rulers, {}>;

0 commit comments

Comments
 (0)