Fixes #90197: Set each individual character width if the whitespace rendering character has a different width than space

This commit is contained in:
Alex Dima 2020-02-10 13:55:03 +01:00
parent f0ad464a3e
commit e0322d3f5c
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
11 changed files with 106 additions and 16 deletions

View file

@ -88,6 +88,7 @@ export interface ISerializedFontInfo {
readonly canUseHalfwidthRightwardsArrow: boolean;
readonly spaceWidth: number;
middotWidth: number;
wsmiddotWidth: number;
readonly maxDigitWidth: number;
}
@ -161,6 +162,7 @@ class CSSBasedConfiguration extends Disposable {
// compatibility with older versions of VS Code which did not store this...
savedFontInfo.fontFeatureSettings = savedFontInfo.fontFeatureSettings || EditorFontLigatures.OFF;
savedFontInfo.middotWidth = savedFontInfo.middotWidth || savedFontInfo.spaceWidth;
savedFontInfo.wsmiddotWidth = savedFontInfo.wsmiddotWidth || savedFontInfo.spaceWidth;
const fontInfo = new FontInfo(savedFontInfo, false);
this._writeToCache(fontInfo, fontInfo);
}
@ -186,6 +188,7 @@ class CSSBasedConfiguration extends Disposable {
canUseHalfwidthRightwardsArrow: readConfig.canUseHalfwidthRightwardsArrow,
spaceWidth: Math.max(readConfig.spaceWidth, 5),
middotWidth: Math.max(readConfig.middotWidth, 5),
wsmiddotWidth: Math.max(readConfig.wsmiddotWidth, 5),
maxDigitWidth: Math.max(readConfig.maxDigitWidth, 5),
}, false);
}
@ -226,9 +229,12 @@ class CSSBasedConfiguration extends Disposable {
const rightwardsArrow = this.createRequest('→', CharWidthRequestType.Regular, all, monospace);
const halfwidthRightwardsArrow = this.createRequest('→', CharWidthRequestType.Regular, all, null);
// middle dot character
// U+00B7 - MIDDLE DOT
const middot = this.createRequest('·', CharWidthRequestType.Regular, all, monospace);
// U+2E31 - WORD SEPARATOR MIDDLE DOT
const wsmiddotWidth = this.createRequest(String.fromCharCode(0x2E31), CharWidthRequestType.Regular, all, null);
// monospace test: some characters
this.createRequest('|', CharWidthRequestType.Regular, all, monospace);
this.createRequest('/', CharWidthRequestType.Regular, all, monospace);
@ -294,6 +300,7 @@ class CSSBasedConfiguration extends Disposable {
canUseHalfwidthRightwardsArrow: canUseHalfwidthRightwardsArrow,
spaceWidth: space.width,
middotWidth: middot.width,
wsmiddotWidth: wsmiddotWidth.width,
maxDigitWidth: maxDigitWidth
}, canTrustBrowserZoomLevel);
}

View file

@ -74,6 +74,7 @@ export class ViewLineOptions {
public readonly renderControlCharacters: boolean;
public readonly spaceWidth: number;
public readonly middotWidth: number;
public readonly wsmiddotWidth: number;
public readonly useMonospaceOptimizations: boolean;
public readonly canUseHalfwidthRightwardsArrow: boolean;
public readonly lineHeight: number;
@ -88,6 +89,7 @@ export class ViewLineOptions {
this.renderControlCharacters = options.get(EditorOption.renderControlCharacters);
this.spaceWidth = fontInfo.spaceWidth;
this.middotWidth = fontInfo.middotWidth;
this.wsmiddotWidth = fontInfo.wsmiddotWidth;
this.useMonospaceOptimizations = (
fontInfo.isMonospace
&& !options.get(EditorOption.disableMonospaceOptimizations)
@ -105,6 +107,7 @@ export class ViewLineOptions {
&& this.renderControlCharacters === other.renderControlCharacters
&& this.spaceWidth === other.spaceWidth
&& this.middotWidth === other.middotWidth
&& this.wsmiddotWidth === other.wsmiddotWidth
&& this.useMonospaceOptimizations === other.useMonospaceOptimizations
&& this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
&& this.lineHeight === other.lineHeight
@ -219,6 +222,7 @@ export class ViewLine implements IVisibleLine {
lineData.startVisibleColumn,
options.spaceWidth,
options.middotWidth,
options.wsmiddotWidth,
options.stopRenderingLineAfter,
options.renderWhitespace,
options.renderControlCharacters,

View file

@ -2163,6 +2163,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
0,
fontInfo.spaceWidth,
fontInfo.middotWidth,
fontInfo.wsmiddotWidth,
options.get(EditorOption.stopRenderingLineAfter),
options.get(EditorOption.renderWhitespace),
options.get(EditorOption.renderControlCharacters),

View file

@ -783,6 +783,7 @@ export class DiffReview extends Disposable {
0,
fontInfo.spaceWidth,
fontInfo.middotWidth,
fontInfo.wsmiddotWidth,
options.get(EditorOption.stopRenderingLineAfter),
options.get(EditorOption.renderWhitespace),
options.get(EditorOption.renderControlCharacters),

View file

@ -135,6 +135,7 @@ export class FontInfo extends BareFontInfo {
readonly canUseHalfwidthRightwardsArrow: boolean;
readonly spaceWidth: number;
readonly middotWidth: number;
readonly wsmiddotWidth: number;
readonly maxDigitWidth: number;
/**
@ -154,6 +155,7 @@ export class FontInfo extends BareFontInfo {
canUseHalfwidthRightwardsArrow: boolean;
spaceWidth: number;
middotWidth: number;
wsmiddotWidth: number;
maxDigitWidth: number;
}, isTrusted: boolean) {
super(opts);
@ -164,6 +166,7 @@ export class FontInfo extends BareFontInfo {
this.canUseHalfwidthRightwardsArrow = opts.canUseHalfwidthRightwardsArrow;
this.spaceWidth = opts.spaceWidth;
this.middotWidth = opts.middotWidth;
this.wsmiddotWidth = opts.wsmiddotWidth;
this.maxDigitWidth = opts.maxDigitWidth;
}
@ -183,6 +186,7 @@ export class FontInfo extends BareFontInfo {
&& this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
&& this.spaceWidth === other.spaceWidth
&& this.middotWidth === other.middotWidth
&& this.wsmiddotWidth === other.wsmiddotWidth
&& this.maxDigitWidth === other.maxDigitWidth
);
}

View file

@ -68,7 +68,8 @@ export class RenderLineInput {
public readonly tabSize: number;
public readonly startVisibleColumn: number;
public readonly spaceWidth: number;
public readonly middotWidth: number;
public readonly renderSpaceWidth: number;
public readonly renderSpaceCharCode: number;
public readonly stopRenderingLineAfter: number;
public readonly renderWhitespace: RenderWhitespace;
public readonly renderControlCharacters: boolean;
@ -94,6 +95,7 @@ export class RenderLineInput {
startVisibleColumn: number,
spaceWidth: number,
middotWidth: number,
wsmiddotWidth: number,
stopRenderingLineAfter: number,
renderWhitespace: 'none' | 'boundary' | 'selection' | 'all',
renderControlCharacters: boolean,
@ -112,7 +114,6 @@ export class RenderLineInput {
this.tabSize = tabSize;
this.startVisibleColumn = startVisibleColumn;
this.spaceWidth = spaceWidth;
this.middotWidth = middotWidth;
this.stopRenderingLineAfter = stopRenderingLineAfter;
this.renderWhitespace = (
renderWhitespace === 'all'
@ -126,6 +127,16 @@ export class RenderLineInput {
this.renderControlCharacters = renderControlCharacters;
this.fontLigatures = fontLigatures;
this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.startOffset < b.startOffset ? -1 : 1);
const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth);
const middotDiff = Math.abs(middotWidth - spaceWidth);
if (wsmiddotDiff < middotDiff) {
this.renderSpaceWidth = wsmiddotWidth;
this.renderSpaceCharCode = 0x2E31; // U+2E31 - WORD SEPARATOR MIDDLE DOT
} else {
this.renderSpaceWidth = middotWidth;
this.renderSpaceCharCode = 0xB7; // U+00B7 - MIDDLE DOT
}
}
private sameSelection(otherSelections: LineRange[] | null): boolean {
@ -162,6 +173,8 @@ export class RenderLineInput {
&& this.tabSize === other.tabSize
&& this.startVisibleColumn === other.startVisibleColumn
&& this.spaceWidth === other.spaceWidth
&& this.renderSpaceWidth === other.renderSpaceWidth
&& this.renderSpaceCharCode === other.renderSpaceCharCode
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
&& this.renderWhitespace === other.renderWhitespace
&& this.renderControlCharacters === other.renderControlCharacters
@ -383,7 +396,7 @@ class ResolvedRenderLineInput {
public readonly startVisibleColumn: number,
public readonly containsRTL: boolean,
public readonly spaceWidth: number,
public readonly middotWidth: number,
public readonly renderSpaceCharCode: number,
public readonly renderWhitespace: RenderWhitespace,
public readonly renderControlCharacters: boolean,
) {
@ -392,7 +405,6 @@ class ResolvedRenderLineInput {
}
function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput {
const useMonospaceOptimizations = input.useMonospaceOptimizations;
const lineContent = input.lineContent;
let isOverflowing: boolean;
@ -408,7 +420,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) {
tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary);
tokens = _applyRenderWhitespace(input, lineContent, len, tokens);
}
let containsForeignElements = ForeignElementType.None;
if (input.lineDecorations.length > 0) {
@ -431,7 +443,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
}
return new ResolvedRenderLineInput(
useMonospaceOptimizations,
input.useMonospaceOptimizations,
input.canUseHalfwidthRightwardsArrow,
lineContent,
len,
@ -443,7 +455,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
input.startVisibleColumn,
input.containsRTL,
input.spaceWidth,
input.middotWidth,
input.renderSpaceCharCode,
input.renderWhitespace,
input.renderControlCharacters
);
@ -553,7 +565,16 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
* 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;.
* The rendering phase will generate `style="width:..."` for these tokens.
*/
function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, startVisibleColumn: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] {
function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len: number, tokens: LinePart[]): LinePart[] {
const continuesWithWrappedLine = input.continuesWithWrappedLine;
const fauxIndentLength = input.fauxIndentLength;
const tabSize = input.tabSize;
const startVisibleColumn = input.startVisibleColumn;
const useMonospaceOptimizations = input.useMonospaceOptimizations;
const selections = input.selectionsOnLine;
const onlyBoundary = (input.renderWhitespace === RenderWhitespace.Boundary);
const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth);
let result: LinePart[] = [], resultLen = 0;
let tokenIndex = 0;
@ -616,7 +637,14 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
// was in whitespace token
if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
// leaving whitespace token or entering a new indent
result[resultLen++] = new LinePart(charIndex, 'mtkw');
if (generateLinePartForEachWhitespace) {
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
for (let i = lastEndIndex + 1; i <= charIndex; i++) {
result[resultLen++] = new LinePart(i, 'mtkw');
}
} else {
result[resultLen++] = new LinePart(charIndex, 'mtkw');
}
tmpIndent = tmpIndent % tabSize;
}
} else {
@ -661,7 +689,18 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
}
}
result[resultLen++] = new LinePart(len, generateWhitespace ? 'mtkw' : tokenType);
if (generateWhitespace) {
if (generateLinePartForEachWhitespace) {
const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
for (let i = lastEndIndex + 1; i <= len; i++) {
result[resultLen++] = new LinePart(i, 'mtkw');
}
} else {
result[resultLen++] = new LinePart(len, 'mtkw');
}
} else {
result[resultLen++] = new LinePart(len, tokenType);
}
return result;
}
@ -739,13 +778,10 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
const startVisibleColumn = input.startVisibleColumn;
const containsRTL = input.containsRTL;
const spaceWidth = input.spaceWidth;
const middotWidth = input.middotWidth;
const renderSpaceCharCode = input.renderSpaceCharCode;
const renderWhitespace = input.renderWhitespace;
const renderControlCharacters = input.renderControlCharacters;
// use U+2E31 - WORD SEPARATOR MIDDLE DOT or U+00B7 - MIDDLE DOT
const spaceRenderWhitespaceCharacter = (middotWidth > spaceWidth ? 0x2E31 : 0xB7);
const characterMapping = new CharacterMapping(len + 1, parts.length);
let charIndex = 0;
@ -815,7 +851,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
} else { // must be CharCode.Space
charWidth = 1;
sb.write1(spaceRenderWhitespaceCharacter); // &middot; or word separator middle dot
sb.write1(renderSpaceCharCode); // &middot; or word separator middle dot
}
charOffsetInPart += charWidth;

View file

@ -127,6 +127,7 @@ export class Colorizer {
0,
0,
0,
0,
-1,
'none',
false,
@ -197,6 +198,7 @@ function _fakeColorize(lines: string[], tabSize: number): string {
0,
0,
0,
0,
-1,
'none',
false,
@ -236,6 +238,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport:
0,
0,
0,
0,
-1,
'none',
false,

View file

@ -42,6 +42,7 @@ export class TestConfiguration extends CommonEditorConfiguration {
canUseHalfwidthRightwardsArrow: true,
spaceWidth: 10,
middotWidth: 10,
wsmiddotWidth: 10,
maxDigitWidth: 10,
}, true);
}

View file

@ -40,6 +40,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
0,
0,
0,
-1,
'none',
false,
@ -92,6 +93,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
0,
0,
0,
-1,
'none',
false,
@ -147,6 +149,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
6,
'boundary',
false,
@ -241,6 +244,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'boundary',
false,
@ -306,6 +310,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -371,6 +376,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -413,6 +419,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -446,6 +453,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -549,6 +557,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -590,6 +599,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -622,6 +632,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -671,6 +682,7 @@ suite('viewLineRenderer.renderLine', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -755,6 +767,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
renderWhitespace,
false,
@ -783,6 +796,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -825,6 +839,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -1242,6 +1257,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
'none',
false,
@ -1285,6 +1301,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
'all',
false,
@ -1320,6 +1337,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
'all',
false,
@ -1356,6 +1374,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
'all',
false,
@ -1388,6 +1407,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1424,6 +1444,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1460,6 +1481,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1493,6 +1515,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1525,6 +1548,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'all',
false,
@ -1563,6 +1587,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1595,6 +1620,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1629,6 +1655,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1662,6 +1689,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'boundary',
false,
@ -1693,6 +1721,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1728,6 +1757,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
10000,
'none',
false,
@ -1759,6 +1789,7 @@ suite('viewLineRenderer.renderLine 2', () => {
0,
10,
10,
10,
-1,
'none',
false,

View file

@ -58,6 +58,7 @@ function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number,
canUseHalfwidthRightwardsArrow: true,
spaceWidth: 7,
middotWidth: 7,
wsmiddotWidth: 7,
maxDigitWidth: 7
}, false);
const lineBreaksComputer = factory.createLineBreaksComputer(fontInfo, tabSize, breakAfter, wrappingIndent);

1
src/vs/monaco.d.ts vendored
View file

@ -4665,6 +4665,7 @@ declare namespace monaco.editor {
readonly canUseHalfwidthRightwardsArrow: boolean;
readonly spaceWidth: number;
readonly middotWidth: number;
readonly wsmiddotWidth: number;
readonly maxDigitWidth: number;
}