diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 11a33fd32a3fecc4fbe4bcd81e308bcbc19fffbb..9a7011dc1d40cc06eb4e964fe22e01f8e7f8adf1 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -42,6 +42,8 @@ const canUseFastRenderedViewLine = (function () { return true; })(); +let monospaceAssumptionsAreValid = true; + const alwaysRenderInlineSelection = (browser.isEdge); export class DomReadingContext { @@ -248,7 +250,7 @@ export class ViewLine implements IVisibleLine { sb.appendASCIIString(''); let renderedViewLine: IRenderedViewLine | null = null; - if (canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) { + if (monospaceAssumptionsAreValid && canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) { if (lineData.content.length < 300 && renderLineInput.lineTokens.getCount() < 100) { // Browser rounding errors have been observed in Chrome and IE, so using the fast // view line only for short lines. Please test before removing the length check... @@ -304,6 +306,29 @@ export class ViewLine implements IVisibleLine { return this._renderedViewLine.getWidthIsFast(); } + public needsMonospaceFontCheck(): boolean { + if (!this._renderedViewLine) { + return false; + } + return (this._renderedViewLine instanceof FastRenderedViewLine); + } + + public monospaceAssumptionsAreValid(): boolean { + if (!this._renderedViewLine) { + return monospaceAssumptionsAreValid; + } + if (this._renderedViewLine instanceof FastRenderedViewLine) { + return this._renderedViewLine.monospaceAssumptionsAreValid(); + } + return monospaceAssumptionsAreValid; + } + + public onMonospaceAssumptionsInvalidated(): void { + if (this._renderedViewLine && this._renderedViewLine instanceof FastRenderedViewLine) { + this._renderedViewLine = this._renderedViewLine.toSlowRenderedLine(); + } + } + public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): VisibleRanges | null { if (!this._renderedViewLine) { return null; @@ -382,6 +407,24 @@ class FastRenderedViewLine implements IRenderedViewLine { return true; } + public monospaceAssumptionsAreValid(): boolean { + if (!this.domNode) { + return monospaceAssumptionsAreValid; + } + const expectedWidth = this.getWidth(); + const actualWidth = (this.domNode.domNode.firstChild).offsetWidth; + if (Math.abs(expectedWidth - actualWidth) >= 2) { + // more than 2px off + console.warn(`monospace assumptions have been violated, therefore disabling monospace optimizations!`); + monospaceAssumptionsAreValid = false; + } + return monospaceAssumptionsAreValid; + } + + public toSlowRenderedLine(): RenderedViewLine { + return createRenderedLine(this.domNode, this.input, this._characterMapping, false, ForeignElementType.None); + } + public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { const startPosition = this._getCharPosition(startColumn); const endPosition = this._getCharPosition(endColumn); diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index b938a5fd9fa882be8588f7df5272d27091cf227a..09ee4fb551603c6271950a1ae44203bf60cc6a22 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./viewLines'; +import * as platform from 'vs/base/common/platform'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Configuration } from 'vs/editor/browser/config/configuration'; @@ -106,6 +107,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // --- width private _maxLineWidth: number; private readonly _asyncUpdateLineWidths: RunOnceScheduler; + private readonly _asyncCheckMonospaceFontAssumptions: RunOnceScheduler; private _horizontalRevealRequest: HorizontalRevealRequest | null; private readonly _lastRenderedData: LastRenderedData; @@ -140,6 +142,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._asyncUpdateLineWidths = new RunOnceScheduler(() => { this._updateLineWidthsSlow(); }, 200); + this._asyncCheckMonospaceFontAssumptions = new RunOnceScheduler(() => { + this._checkMonospaceFontAssumptions(); + }, 2000); this._lastRenderedData = new LastRenderedData(); @@ -148,6 +153,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, public dispose(): void { this._asyncUpdateLineWidths.dispose(); + this._asyncCheckMonospaceFontAssumptions.dispose(); super.dispose(); } @@ -513,6 +519,37 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return allWidthsComputed; } + private _checkMonospaceFontAssumptions(): void { + // Problems with monospace assumptions are more apparent for longer lines, + // as small rounding errors start to sum up, so we will select the longest + // line for a closer inspection + let longestLineNumber = -1; + let longestWidth = -1; + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + if (visibleLine.needsMonospaceFontCheck()) { + const lineWidth = visibleLine.getWidth(); + if (lineWidth > longestWidth) { + longestWidth = lineWidth; + longestLineNumber = lineNumber; + } + } + } + + if (longestLineNumber === -1) { + return; + } + + if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) { + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + visibleLine.onMonospaceAssumptionsInvalidated(); + } + } + } + public prepareRender(): void { throw new Error('Not supported'); } @@ -571,6 +608,18 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._asyncUpdateLineWidths.schedule(); } + if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) { + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + if (visibleLine.needsMonospaceFontCheck()) { + this._asyncCheckMonospaceFontAssumptions.schedule(); + break; + } + } + } + // (3) handle scrolling this._linesContent.setLayerHinting(this._canUseLayerHinting); this._linesContent.setContain('strict');