Skip to content

Commit e0322d3

Browse files
committed
Fixes microsoft#90197: Set each individual character width if the whitespace rendering character has a different width than space
1 parent f0ad464 commit e0322d3

11 files changed

Lines changed: 106 additions & 16 deletions

File tree

src/vs/editor/browser/config/configuration.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export interface ISerializedFontInfo {
8888
readonly canUseHalfwidthRightwardsArrow: boolean;
8989
readonly spaceWidth: number;
9090
middotWidth: number;
91+
wsmiddotWidth: number;
9192
readonly maxDigitWidth: number;
9293
}
9394

@@ -161,6 +162,7 @@ class CSSBasedConfiguration extends Disposable {
161162
// compatibility with older versions of VS Code which did not store this...
162163
savedFontInfo.fontFeatureSettings = savedFontInfo.fontFeatureSettings || EditorFontLigatures.OFF;
163164
savedFontInfo.middotWidth = savedFontInfo.middotWidth || savedFontInfo.spaceWidth;
165+
savedFontInfo.wsmiddotWidth = savedFontInfo.wsmiddotWidth || savedFontInfo.spaceWidth;
164166
const fontInfo = new FontInfo(savedFontInfo, false);
165167
this._writeToCache(fontInfo, fontInfo);
166168
}
@@ -186,6 +188,7 @@ class CSSBasedConfiguration extends Disposable {
186188
canUseHalfwidthRightwardsArrow: readConfig.canUseHalfwidthRightwardsArrow,
187189
spaceWidth: Math.max(readConfig.spaceWidth, 5),
188190
middotWidth: Math.max(readConfig.middotWidth, 5),
191+
wsmiddotWidth: Math.max(readConfig.wsmiddotWidth, 5),
189192
maxDigitWidth: Math.max(readConfig.maxDigitWidth, 5),
190193
}, false);
191194
}
@@ -226,9 +229,12 @@ class CSSBasedConfiguration extends Disposable {
226229
const rightwardsArrow = this.createRequest('→', CharWidthRequestType.Regular, all, monospace);
227230
const halfwidthRightwardsArrow = this.createRequest('→', CharWidthRequestType.Regular, all, null);
228231

229-
// middle dot character
232+
// U+00B7 - MIDDLE DOT
230233
const middot = this.createRequest('·', CharWidthRequestType.Regular, all, monospace);
231234

235+
// U+2E31 - WORD SEPARATOR MIDDLE DOT
236+
const wsmiddotWidth = this.createRequest(String.fromCharCode(0x2E31), CharWidthRequestType.Regular, all, null);
237+
232238
// monospace test: some characters
233239
this.createRequest('|', CharWidthRequestType.Regular, all, monospace);
234240
this.createRequest('/', CharWidthRequestType.Regular, all, monospace);
@@ -294,6 +300,7 @@ class CSSBasedConfiguration extends Disposable {
294300
canUseHalfwidthRightwardsArrow: canUseHalfwidthRightwardsArrow,
295301
spaceWidth: space.width,
296302
middotWidth: middot.width,
303+
wsmiddotWidth: wsmiddotWidth.width,
297304
maxDigitWidth: maxDigitWidth
298305
}, canTrustBrowserZoomLevel);
299306
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export class ViewLineOptions {
7474
public readonly renderControlCharacters: boolean;
7575
public readonly spaceWidth: number;
7676
public readonly middotWidth: number;
77+
public readonly wsmiddotWidth: number;
7778
public readonly useMonospaceOptimizations: boolean;
7879
public readonly canUseHalfwidthRightwardsArrow: boolean;
7980
public readonly lineHeight: number;
@@ -88,6 +89,7 @@ export class ViewLineOptions {
8889
this.renderControlCharacters = options.get(EditorOption.renderControlCharacters);
8990
this.spaceWidth = fontInfo.spaceWidth;
9091
this.middotWidth = fontInfo.middotWidth;
92+
this.wsmiddotWidth = fontInfo.wsmiddotWidth;
9193
this.useMonospaceOptimizations = (
9294
fontInfo.isMonospace
9395
&& !options.get(EditorOption.disableMonospaceOptimizations)
@@ -105,6 +107,7 @@ export class ViewLineOptions {
105107
&& this.renderControlCharacters === other.renderControlCharacters
106108
&& this.spaceWidth === other.spaceWidth
107109
&& this.middotWidth === other.middotWidth
110+
&& this.wsmiddotWidth === other.wsmiddotWidth
108111
&& this.useMonospaceOptimizations === other.useMonospaceOptimizations
109112
&& this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
110113
&& this.lineHeight === other.lineHeight
@@ -219,6 +222,7 @@ export class ViewLine implements IVisibleLine {
219222
lineData.startVisibleColumn,
220223
options.spaceWidth,
221224
options.middotWidth,
225+
options.wsmiddotWidth,
222226
options.stopRenderingLineAfter,
223227
options.renderWhitespace,
224228
options.renderControlCharacters,

src/vs/editor/browser/widget/diffEditorWidget.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,6 +2163,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
21632163
0,
21642164
fontInfo.spaceWidth,
21652165
fontInfo.middotWidth,
2166+
fontInfo.wsmiddotWidth,
21662167
options.get(EditorOption.stopRenderingLineAfter),
21672168
options.get(EditorOption.renderWhitespace),
21682169
options.get(EditorOption.renderControlCharacters),

src/vs/editor/browser/widget/diffReview.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ export class DiffReview extends Disposable {
783783
0,
784784
fontInfo.spaceWidth,
785785
fontInfo.middotWidth,
786+
fontInfo.wsmiddotWidth,
786787
options.get(EditorOption.stopRenderingLineAfter),
787788
options.get(EditorOption.renderWhitespace),
788789
options.get(EditorOption.renderControlCharacters),

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export class FontInfo extends BareFontInfo {
135135
readonly canUseHalfwidthRightwardsArrow: boolean;
136136
readonly spaceWidth: number;
137137
readonly middotWidth: number;
138+
readonly wsmiddotWidth: number;
138139
readonly maxDigitWidth: number;
139140

140141
/**
@@ -154,6 +155,7 @@ export class FontInfo extends BareFontInfo {
154155
canUseHalfwidthRightwardsArrow: boolean;
155156
spaceWidth: number;
156157
middotWidth: number;
158+
wsmiddotWidth: number;
157159
maxDigitWidth: number;
158160
}, isTrusted: boolean) {
159161
super(opts);
@@ -164,6 +166,7 @@ export class FontInfo extends BareFontInfo {
164166
this.canUseHalfwidthRightwardsArrow = opts.canUseHalfwidthRightwardsArrow;
165167
this.spaceWidth = opts.spaceWidth;
166168
this.middotWidth = opts.middotWidth;
169+
this.wsmiddotWidth = opts.wsmiddotWidth;
167170
this.maxDigitWidth = opts.maxDigitWidth;
168171
}
169172

@@ -183,6 +186,7 @@ export class FontInfo extends BareFontInfo {
183186
&& this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
184187
&& this.spaceWidth === other.spaceWidth
185188
&& this.middotWidth === other.middotWidth
189+
&& this.wsmiddotWidth === other.wsmiddotWidth
186190
&& this.maxDigitWidth === other.maxDigitWidth
187191
);
188192
}

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

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ export class RenderLineInput {
6868
public readonly tabSize: number;
6969
public readonly startVisibleColumn: number;
7070
public readonly spaceWidth: number;
71-
public readonly middotWidth: number;
71+
public readonly renderSpaceWidth: number;
72+
public readonly renderSpaceCharCode: number;
7273
public readonly stopRenderingLineAfter: number;
7374
public readonly renderWhitespace: RenderWhitespace;
7475
public readonly renderControlCharacters: boolean;
@@ -94,6 +95,7 @@ export class RenderLineInput {
9495
startVisibleColumn: number,
9596
spaceWidth: number,
9697
middotWidth: number,
98+
wsmiddotWidth: number,
9799
stopRenderingLineAfter: number,
98100
renderWhitespace: 'none' | 'boundary' | 'selection' | 'all',
99101
renderControlCharacters: boolean,
@@ -112,7 +114,6 @@ export class RenderLineInput {
112114
this.tabSize = tabSize;
113115
this.startVisibleColumn = startVisibleColumn;
114116
this.spaceWidth = spaceWidth;
115-
this.middotWidth = middotWidth;
116117
this.stopRenderingLineAfter = stopRenderingLineAfter;
117118
this.renderWhitespace = (
118119
renderWhitespace === 'all'
@@ -126,6 +127,16 @@ export class RenderLineInput {
126127
this.renderControlCharacters = renderControlCharacters;
127128
this.fontLigatures = fontLigatures;
128129
this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.startOffset < b.startOffset ? -1 : 1);
130+
131+
const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth);
132+
const middotDiff = Math.abs(middotWidth - spaceWidth);
133+
if (wsmiddotDiff < middotDiff) {
134+
this.renderSpaceWidth = wsmiddotWidth;
135+
this.renderSpaceCharCode = 0x2E31; // U+2E31 - WORD SEPARATOR MIDDLE DOT
136+
} else {
137+
this.renderSpaceWidth = middotWidth;
138+
this.renderSpaceCharCode = 0xB7; // U+00B7 - MIDDLE DOT
139+
}
129140
}
130141

131142
private sameSelection(otherSelections: LineRange[] | null): boolean {
@@ -162,6 +173,8 @@ export class RenderLineInput {
162173
&& this.tabSize === other.tabSize
163174
&& this.startVisibleColumn === other.startVisibleColumn
164175
&& this.spaceWidth === other.spaceWidth
176+
&& this.renderSpaceWidth === other.renderSpaceWidth
177+
&& this.renderSpaceCharCode === other.renderSpaceCharCode
165178
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
166179
&& this.renderWhitespace === other.renderWhitespace
167180
&& this.renderControlCharacters === other.renderControlCharacters
@@ -383,7 +396,7 @@ class ResolvedRenderLineInput {
383396
public readonly startVisibleColumn: number,
384397
public readonly containsRTL: boolean,
385398
public readonly spaceWidth: number,
386-
public readonly middotWidth: number,
399+
public readonly renderSpaceCharCode: number,
387400
public readonly renderWhitespace: RenderWhitespace,
388401
public readonly renderControlCharacters: boolean,
389402
) {
@@ -392,7 +405,6 @@ class ResolvedRenderLineInput {
392405
}
393406

394407
function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput {
395-
const useMonospaceOptimizations = input.useMonospaceOptimizations;
396408
const lineContent = input.lineContent;
397409

398410
let isOverflowing: boolean;
@@ -408,7 +420,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
408420

409421
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
410422
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) {
411-
tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary);
423+
tokens = _applyRenderWhitespace(input, lineContent, len, tokens);
412424
}
413425
let containsForeignElements = ForeignElementType.None;
414426
if (input.lineDecorations.length > 0) {
@@ -431,7 +443,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
431443
}
432444

433445
return new ResolvedRenderLineInput(
434-
useMonospaceOptimizations,
446+
input.useMonospaceOptimizations,
435447
input.canUseHalfwidthRightwardsArrow,
436448
lineContent,
437449
len,
@@ -443,7 +455,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
443455
input.startVisibleColumn,
444456
input.containsRTL,
445457
input.spaceWidth,
446-
input.middotWidth,
458+
input.renderSpaceCharCode,
447459
input.renderWhitespace,
448460
input.renderControlCharacters
449461
);
@@ -553,7 +565,16 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
553565
* Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
554566
* The rendering phase will generate `style="width:..."` for these tokens.
555567
*/
556-
function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, startVisibleColumn: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] {
568+
function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len: number, tokens: LinePart[]): LinePart[] {
569+
570+
const continuesWithWrappedLine = input.continuesWithWrappedLine;
571+
const fauxIndentLength = input.fauxIndentLength;
572+
const tabSize = input.tabSize;
573+
const startVisibleColumn = input.startVisibleColumn;
574+
const useMonospaceOptimizations = input.useMonospaceOptimizations;
575+
const selections = input.selectionsOnLine;
576+
const onlyBoundary = (input.renderWhitespace === RenderWhitespace.Boundary);
577+
const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth);
557578

558579
let result: LinePart[] = [], resultLen = 0;
559580
let tokenIndex = 0;
@@ -616,7 +637,14 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
616637
// was in whitespace token
617638
if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
618639
// leaving whitespace token or entering a new indent
619-
result[resultLen++] = new LinePart(charIndex, 'mtkw');
640+
if (generateLinePartForEachWhitespace) {
641+
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
642+
for (let i = lastEndIndex + 1; i <= charIndex; i++) {
643+
result[resultLen++] = new LinePart(i, 'mtkw');
644+
}
645+
} else {
646+
result[resultLen++] = new LinePart(charIndex, 'mtkw');
647+
}
620648
tmpIndent = tmpIndent % tabSize;
621649
}
622650
} else {
@@ -661,7 +689,18 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
661689
}
662690
}
663691

664-
result[resultLen++] = new LinePart(len, generateWhitespace ? 'mtkw' : tokenType);
692+
if (generateWhitespace) {
693+
if (generateLinePartForEachWhitespace) {
694+
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
695+
for (let i = lastEndIndex + 1; i <= len; i++) {
696+
result[resultLen++] = new LinePart(i, 'mtkw');
697+
}
698+
} else {
699+
result[resultLen++] = new LinePart(len, 'mtkw');
700+
}
701+
} else {
702+
result[resultLen++] = new LinePart(len, tokenType);
703+
}
665704

666705
return result;
667706
}
@@ -739,13 +778,10 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
739778
const startVisibleColumn = input.startVisibleColumn;
740779
const containsRTL = input.containsRTL;
741780
const spaceWidth = input.spaceWidth;
742-
const middotWidth = input.middotWidth;
781+
const renderSpaceCharCode = input.renderSpaceCharCode;
743782
const renderWhitespace = input.renderWhitespace;
744783
const renderControlCharacters = input.renderControlCharacters;
745784

746-
// use U+2E31 - WORD SEPARATOR MIDDLE DOT or U+00B7 - MIDDLE DOT
747-
const spaceRenderWhitespaceCharacter = (middotWidth > spaceWidth ? 0x2E31 : 0xB7);
748-
749785
const characterMapping = new CharacterMapping(len + 1, parts.length);
750786

751787
let charIndex = 0;
@@ -815,7 +851,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
815851
} else { // must be CharCode.Space
816852
charWidth = 1;
817853

818-
sb.write1(spaceRenderWhitespaceCharacter); // &middot; or word separator middle dot
854+
sb.write1(renderSpaceCharCode); // &middot; or word separator middle dot
819855
}
820856

821857
charOffsetInPart += charWidth;

src/vs/editor/standalone/browser/colorizer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export class Colorizer {
127127
0,
128128
0,
129129
0,
130+
0,
130131
-1,
131132
'none',
132133
false,
@@ -197,6 +198,7 @@ function _fakeColorize(lines: string[], tabSize: number): string {
197198
0,
198199
0,
199200
0,
201+
0,
200202
-1,
201203
'none',
202204
false,
@@ -236,6 +238,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport:
236238
0,
237239
0,
238240
0,
241+
0,
239242
-1,
240243
'none',
241244
false,

src/vs/editor/test/common/mocks/testConfiguration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class TestConfiguration extends CommonEditorConfiguration {
4242
canUseHalfwidthRightwardsArrow: true,
4343
spaceWidth: 10,
4444
middotWidth: 10,
45+
wsmiddotWidth: 10,
4546
maxDigitWidth: 10,
4647
}, true);
4748
}

0 commit comments

Comments
 (0)