diff options
author | Connor Peet <connor@peet.io> | 2019-10-14 20:53:34 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-14 20:53:34 +0300 |
commit | 5767f6f21b6bb00a733a49fb3335aaa1c2501497 (patch) | |
tree | 6560608d1a2676e6d5ce6056434755a648f04515 | |
parent | aad1e68331c208156b923cd1b499436cefd5f3dc (diff) |
minimap - allow variable scaling (#82265)
* minimap - allow variable scaling
This PR allows the minimap to be scaled to several constant values. Most of the
work in this PR is adjusting the the font renderer to render character at
variable sizes. It turns out most generic image scaling algorithms are not
built to scale down to one or two pixels (the default minimap font size has
1px by 2px characters), so some work was needed to make this possible and look
good.
Generating fonts at runtime does incur a small performance penalty, taking
about 0.6m at 1x scale and 0.9ms at 4x scale on my machine to create the font
the first time we render a minimap. If we want to avoid this, we could consider
shipping pre-rendered font for the first few scale settings.
At this moment this only supports scaling to a constant integer--effectively,
scaling the character width, since we start at 1x2px. More granular scaling
would be interesting, but will come at a runtime cost as we'll need to do
linear interpolation for each character we draw at a non-integral coordinate.
Draw speed is comparable to the previous version, the profiler reported in
the range of 8-11ms to render my test file in both the previous and new code.
I've tested this on my high DPI Macbook display and it appears to work well
there too.
Talking to Alex, something we may need to look into is matching the user font
and render settings. Previously, and continuing in this PR, we use the default
monospace font on the system with a restricted set of character codes.
Previously the sidebar's font was too small to be visible, but now its content
can be seen under large settings. We may need to look and reworking how this
data is rendered. Perhaps we generate the characters we need on the fly into
their own buffers? Open to ideas.
Fixes https://github.com/microsoft/vscode/issues/21773
* fixup! not caching created factory
* fix common/browser component layering
* fixup! use a constant upscale for hDPI
* small tweaks
* fixup! pr comments
* fixup! reduce max minimap scale
16 files changed, 505 insertions, 1701 deletions
diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index da56ba7e095..f44cbd236b8 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -60,7 +60,7 @@ export module Iterator { }; } - export function fromArray<T>(array: T[], index = 0, length = array.length): Iterator<T> { + export function fromArray<T>(array: ReadonlyArray<T>, index = 0, length = array.length): Iterator<T> { return { next(): IteratorResult<T> { if (index >= length) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index b32b8d83775..146ce38adc4 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -18,9 +18,10 @@ import { Range } from 'vs/editor/common/core/range'; import { RGBA8 } from 'vs/editor/common/core/rgba'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; import { ColorId } from 'vs/editor/common/modes'; -import { Constants, MinimapCharRenderer, MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer'; +import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; +import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; -import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; @@ -30,33 +31,22 @@ import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel' import { Selection } from 'vs/editor/common/core/selection'; import { Color } from 'vs/base/common/color'; import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch'; +import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; -function getMinimapLineHeight(renderMinimap: RenderMinimap): number { - if (renderMinimap === RenderMinimap.Large) { - return Constants.x2_CHAR_HEIGHT; +function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number { + if (renderMinimap === RenderMinimap.Text) { + return Constants.BASE_CHAR_HEIGHT * scale; } - if (renderMinimap === RenderMinimap.LargeBlocks) { - return Constants.x2_CHAR_HEIGHT + 2; - } - if (renderMinimap === RenderMinimap.Small) { - return Constants.x1_CHAR_HEIGHT; - } - // RenderMinimap.SmallBlocks - return Constants.x1_CHAR_HEIGHT + 1; + // RenderMinimap.Blocks + return (Constants.BASE_CHAR_HEIGHT + 1) * scale; } -function getMinimapCharWidth(renderMinimap: RenderMinimap): number { - if (renderMinimap === RenderMinimap.Large) { - return Constants.x2_CHAR_WIDTH; - } - if (renderMinimap === RenderMinimap.LargeBlocks) { - return Constants.x2_CHAR_WIDTH; - } - if (renderMinimap === RenderMinimap.Small) { - return Constants.x1_CHAR_WIDTH; +function getMinimapCharWidth(renderMinimap: RenderMinimap, scale: number): number { + if (renderMinimap === RenderMinimap.Text) { + return Constants.BASE_CHAR_WIDTH * scale; } - // RenderMinimap.SmallBlocks - return Constants.x1_CHAR_WIDTH; + // RenderMinimap.Blocks + return Constants.BASE_CHAR_WIDTH * scale; } /** @@ -78,6 +68,10 @@ class MinimapOptions { public readonly lineHeight: number; + public readonly fontScale: number; + + public readonly charRenderer: MinimapCharRenderer; + /** * container dom node left position (in CSS px) */ @@ -119,6 +113,8 @@ class MinimapOptions { this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; + this.fontScale = Math.round(minimapOpts.scale * pixelRatio); + this.charRenderer = MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -140,6 +136,7 @@ class MinimapOptions { && this.pixelRatio === other.pixelRatio && this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth && this.lineHeight === other.lineHeight + && this.fontScale === other.fontScale && this.minimapLeft === other.minimapLeft && this.minimapWidth === other.minimapWidth && this.minimapHeight === other.minimapHeight @@ -225,7 +222,7 @@ class MinimapLayout { previousLayout: MinimapLayout | null ): MinimapLayout { const pixelRatio = options.pixelRatio; - const minimapLineHeight = getMinimapLineHeight(options.renderMinimap); + const minimapLineHeight = getMinimapLineHeight(options.renderMinimap, options.fontScale); const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight); const lineHeight = options.lineHeight; @@ -524,7 +521,7 @@ export class Minimap extends ViewPart { if (!this._lastRenderData) { return; } - const minimapLineHeight = getMinimapLineHeight(renderMinimap); + const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); const internalOffsetY = this._options.pixelRatio * e.browserEvent.offsetY; const lineIndex = Math.floor(internalOffsetY / minimapLineHeight); @@ -778,7 +775,7 @@ export class Minimap extends ViewPart { // Compute horizontal slider coordinates const scrollLeftChars = renderingCtx.scrollLeft / this._options.typicalHalfwidthCharacterWidth; - const horizontalSliderLeft = Math.min(this._options.minimapWidth, Math.round(scrollLeftChars * getMinimapCharWidth(this._options.renderMinimap) / this._options.pixelRatio)); + const horizontalSliderLeft = Math.min(this._options.minimapWidth, Math.round(scrollLeftChars * getMinimapCharWidth(this._options.renderMinimap, this._options.fontScale) / this._options.pixelRatio)); this._sliderHorizontal.setLeft(horizontalSliderLeft); this._sliderHorizontal.setWidth(this._options.minimapWidth - horizontalSliderLeft); this._sliderHorizontal.setTop(0); @@ -794,8 +791,8 @@ export class Minimap extends ViewPart { const decorations = this._context.model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._context.model.getLineMaxColumn(layout.endLineNumber))); const { renderMinimap, canvasInnerWidth, canvasInnerHeight } = this._options; - const lineHeight = getMinimapLineHeight(renderMinimap); - const characterWidth = getMinimapCharWidth(renderMinimap); + const lineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); + const characterWidth = getMinimapCharWidth(renderMinimap, this._options.fontScale); const tabSize = this._context.model.getOptions().tabSize; const canvasContext = this._decorationsCanvas.domNode.getContext('2d')!; @@ -890,7 +887,7 @@ export class Minimap extends ViewPart { const renderMinimap = this._options.renderMinimap; const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; - const minimapLineHeight = getMinimapLineHeight(renderMinimap); + const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); // Check if nothing changed w.r.t. lines from last frame if (this._lastRenderData && this._lastRenderData.linesEquals(layout)) { @@ -929,10 +926,11 @@ export class Minimap extends ViewPart { useLighterFont, renderMinimap, this._tokensColorTracker, - getOrCreateMinimapCharRenderer(), + this._options.charRenderer, dy, tabSize, - lineInfo.data[lineIndex]! + lineInfo.data[lineIndex]!, + this._options.fontScale ); } renderedLines[lineIndex] = new MinimapLine(dy); @@ -1056,11 +1054,12 @@ export class Minimap extends ViewPart { minimapCharRenderer: MinimapCharRenderer, dy: number, tabSize: number, - lineData: ViewLineData + lineData: ViewLineData, + fontScale: number ): void { const content = lineData.content; const tokens = lineData.tokens; - const charWidth = getMinimapCharWidth(renderMinimap); + const charWidth = getMinimapCharWidth(renderMinimap, fontScale); const maxDx = target.width - charWidth; let dx = 0; @@ -1092,16 +1091,12 @@ export class Minimap extends ViewPart { const count = strings.isFullWidthCharacter(charCode) ? 2 : 1; for (let i = 0; i < count; i++) { - if (renderMinimap === RenderMinimap.Large) { - minimapCharRenderer.x2RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); - } else if (renderMinimap === RenderMinimap.Small) { - minimapCharRenderer.x1RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); - } else if (renderMinimap === RenderMinimap.LargeBlocks) { - minimapCharRenderer.x2BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); - } else { - // RenderMinimap.SmallBlocks - minimapCharRenderer.x1BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); + if (renderMinimap === RenderMinimap.Blocks) { + minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); + } else { // RenderMinimap.Text + minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); } + dx += charWidth; if (dx > maxDx) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts new file mode 100644 index 00000000000..e1136ab6c28 --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RGBA8 } from 'vs/editor/common/core/rgba'; +import { Constants, getCharIndex } from './minimapCharSheet'; + +export class MinimapCharRenderer { + _minimapCharRendererBrand: void; + + private readonly charDataNormal: Uint8ClampedArray; + private readonly charDataLight: Uint8ClampedArray; + + constructor(charData: Uint8ClampedArray, public readonly scale: number) { + this.charDataNormal = MinimapCharRenderer.soften(charData, 12 / 15); + this.charDataLight = MinimapCharRenderer.soften(charData, 50 / 60); + } + + private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray { + let result = new Uint8ClampedArray(input.length); + for (let i = 0, len = input.length; i < len; i++) { + result[i] = input[i] * ratio; + } + return result; + } + + public renderChar( + target: ImageData, + dx: number, + dy: number, + chCode: number, + color: RGBA8, + backgroundColor: RGBA8, + useLighterFont: boolean + ): void { + const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; + const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; + if (dx + charWidth > target.width || dy + charHeight > target.height) { + console.warn('bad render request outside image data'); + return; + } + + const charData = useLighterFont ? this.charDataLight : this.charDataNormal; + const charIndex = getCharIndex(chCode); + + const destWidth = target.width * Constants.RGBA_CHANNELS_CNT; + + const backgroundR = backgroundColor.r; + const backgroundG = backgroundColor.g; + const backgroundB = backgroundColor.b; + + const deltaR = color.r - backgroundR; + const deltaG = color.g - backgroundG; + const deltaB = color.b - backgroundB; + + const dest = target.data; + let sourceOffset = charIndex * charWidth * charHeight; + + let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; + for (let y = 0; y < charHeight; y++) { + let column = row; + for (let x = 0; x < charWidth; x++) { + const c = charData[sourceOffset++] / 255; + dest[column++] = backgroundR + deltaR * c; + dest[column++] = backgroundG + deltaG * c; + dest[column++] = backgroundB + deltaB * c; + column++; + } + + row += destWidth; + } + } + + public blockRenderChar( + target: ImageData, + dx: number, + dy: number, + color: RGBA8, + backgroundColor: RGBA8, + useLighterFont: boolean + ): void { + const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; + const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; + if (dx + charWidth > target.width || dy + charHeight > target.height) { + console.warn('bad render request outside image data'); + return; + } + + const destWidth = target.width * Constants.RGBA_CHANNELS_CNT; + + const c = 0.5; + + const backgroundR = backgroundColor.r; + const backgroundG = backgroundColor.g; + const backgroundB = backgroundColor.b; + + const deltaR = color.r - backgroundR; + const deltaG = color.g - backgroundG; + const deltaB = color.b - backgroundB; + + const colorR = backgroundR + deltaR * c; + const colorG = backgroundG + deltaG * c; + const colorB = backgroundB + deltaB * c; + + const dest = target.data; + + let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; + for (let y = 0; y < charHeight; y++) { + let column = row; + for (let x = 0; x < charWidth; x++) { + dest[column++] = colorR; + dest[column++] = colorG; + dest[column++] = colorB; + column++; + } + + row += destWidth; + } + } +} diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts new file mode 100644 index 00000000000..78090d0274c --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; +import { allCharCodes } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { Constants } from './minimapCharSheet'; + +/** + * Creates character renderers. It takes a 'scale' that determines how large + * characters should be drawn. Using this, it draws data into a canvas and + * then downsamples the characters as necessary for the current display. + * This makes rendering more efficient, rather than drawing a full (tiny) + * font, or downsampling in real-time. + */ +export class MinimapCharRendererFactory { + private static lastCreated?: MinimapCharRenderer; + private static lastFontFamily?: string; + + /** + * Creates a new character renderer factory with the given scale. + */ + public static create(scale: number, fontFamily: string) { + // renderers are immutable. By default we'll 'create' a new minimap + // character renderer whenever we switch editors, no need to do extra work. + if (this.lastCreated && scale === this.lastCreated.scale && fontFamily === this.lastFontFamily) { + return this.lastCreated; + } + + const factory = MinimapCharRendererFactory.createFromSampleData( + MinimapCharRendererFactory.createSampleData(fontFamily).data, + scale + ); + this.lastCreated = factory; + return factory; + } + + /** + * Creates the font sample data, writing to a canvas. + */ + public static createSampleData(fontFamily: string): ImageData { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d')!; + + canvas.style.height = `${Constants.SAMPLED_CHAR_HEIGHT}px`; + canvas.height = Constants.SAMPLED_CHAR_HEIGHT; + canvas.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH; + canvas.style.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH + 'px'; + + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${Constants.SAMPLED_CHAR_HEIGHT}px ${fontFamily}`; + ctx.textBaseline = 'middle'; + + let x = 0; + for (const code of allCharCodes) { + ctx.fillText(String.fromCharCode(code), x, Constants.SAMPLED_CHAR_HEIGHT / 2); + x += Constants.SAMPLED_CHAR_WIDTH; + } + + return ctx.getImageData(0, 0, Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH, Constants.SAMPLED_CHAR_HEIGHT); + } + + /** + * Creates a character renderer from the canvas sample data. + */ + public static createFromSampleData(source: Uint8ClampedArray, scale: number): MinimapCharRenderer { + const expectedLength = + Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT; + if (source.length !== expectedLength) { + throw new Error('Unexpected source in MinimapCharRenderer'); + } + + let charData = MinimapCharRendererFactory._downsample(source, scale); + return new MinimapCharRenderer(charData, scale); + } + + private static _downsampleChar( + source: Uint8ClampedArray, + sourceOffset: number, + dest: Uint8ClampedArray, + destOffset: number, + scale: number + ): number { + const width = Constants.BASE_CHAR_WIDTH * scale; + const height = Constants.BASE_CHAR_HEIGHT * scale; + + let targetIndex = destOffset; + let brightest = 0; + + // This is essentially an ad-hoc rescaling algorithm. Standard approaches + // like bicubic interpolation are awesome for scaling between image sizes, + // but don't work so well when scaling to very small pixel values, we end + // up with blurry, indistinct forms. + // + // The approach taken here is simply mapping each source pixel to the target + // pixels, and taking the weighted values for all pixels in each, and then + // averaging them out. Finally we apply an intensity boost in _downsample, + // since when scaling to the smallest pixel sizes there's more black space + // which causes characters to be much less distinct. + for (let y = 0; y < height; y++) { + // 1. For this destination pixel, get the source pixels we're sampling + // from (x1, y1) to the next pixel (x2, y2) + const sourceY1 = (y / height) * Constants.SAMPLED_CHAR_HEIGHT; + const sourceY2 = ((y + 1) / height) * Constants.SAMPLED_CHAR_HEIGHT; + + for (let x = 0; x < width; x++) { + const sourceX1 = (x / width) * Constants.SAMPLED_CHAR_WIDTH; + const sourceX2 = ((x + 1) / width) * Constants.SAMPLED_CHAR_WIDTH; + + // 2. Sample all of them, summing them up and weighting them. Similar + // to bilinear interpolation. + let value = 0; + let samples = 0; + for (let sy = sourceY1; sy < sourceY2; sy++) { + const sourceRow = sourceOffset + Math.floor(sy) * Constants.RGBA_SAMPLED_ROW_WIDTH; + const yBalance = 1 - (sy - Math.floor(sy)); + for (let sx = sourceX1; sx < sourceX2; sx++) { + const xBalance = 1 - (sx - Math.floor(sx)); + const sourceIndex = sourceRow + Math.floor(sx) * Constants.RGBA_CHANNELS_CNT; + + const weight = xBalance * yBalance; + samples += weight; + value += ((source[sourceIndex] * source[sourceIndex + 3]) / 255) * weight; + } + } + + const final = value / samples; + brightest = Math.max(brightest, final); + dest[targetIndex++] = final; + } + } + + return brightest; + } + + private static _downsample(data: Uint8ClampedArray, scale: number): Uint8ClampedArray { + const pixelsPerCharacter = Constants.BASE_CHAR_HEIGHT * scale * Constants.BASE_CHAR_WIDTH * scale; + const resultLen = pixelsPerCharacter * Constants.CHAR_COUNT; + const result = new Uint8ClampedArray(resultLen); + + let resultOffset = 0; + let sourceOffset = 0; + let brightest = 0; + for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { + brightest = Math.max(brightest, this._downsampleChar(data, sourceOffset, result, resultOffset, scale)); + resultOffset += pixelsPerCharacter; + sourceOffset += Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT; + } + + if (brightest > 0) { + const adjust = 255 / brightest; + for (let i = 0; i < resultLen; i++) { + result[i] *= adjust; + } + } + + return result; + } +} diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts new file mode 100644 index 00000000000..af7b0eae4f8 --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const enum Constants { + START_CH_CODE = 32, // Space + END_CH_CODE = 126, // Tilde (~) + UNKNOWN_CODE = 65533, // UTF placeholder code + CHAR_COUNT = END_CH_CODE - START_CH_CODE + 2, + + SAMPLED_CHAR_HEIGHT = 16, + SAMPLED_CHAR_WIDTH = 10, + + BASE_CHAR_HEIGHT = 2, + BASE_CHAR_WIDTH = 1, + + RGBA_CHANNELS_CNT = 4, + RGBA_SAMPLED_ROW_WIDTH = RGBA_CHANNELS_CNT * CHAR_COUNT * SAMPLED_CHAR_WIDTH +} + +export const allCharCodes: ReadonlyArray<number> = (() => { + const v: number[] = []; + for (let i = Constants.START_CH_CODE; i <= Constants.END_CH_CODE; i++) { + v.push(i); + } + + v.push(Constants.UNKNOWN_CODE); + return v; +})(); + +export const getCharIndex = (chCode: number) => { + chCode -= Constants.START_CH_CODE; + if (chCode < 0 || chCode > Constants.CHAR_COUNT) { + return Constants.CHAR_COUNT - 1; // unknown symbol + } + + return chCode; +}; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 44a7959ddcf..f59153b0c80 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1409,10 +1409,8 @@ export interface OverviewRulerPosition { export const enum RenderMinimap { None = 0, - Small = 1, - Large = 2, - SmallBlocks = 3, - LargeBlocks = 4, + Text = 1, + Blocks = 2, } /** @@ -1568,6 +1566,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption. const minimapEnabled = minimap.enabled; const minimapSide = minimap.side; const minimapRenderCharacters = minimap.renderCharacters; + const minimapScale = (pixelRatio >= 2 ? Math.round(minimap.scale * 2) : minimap.scale); const minimapMaxColumn = minimap.maxColumn | 0; const scrollbar = options.get(EditorOption.scrollbar); @@ -1618,14 +1617,10 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption. renderMinimap = RenderMinimap.None; contentWidth = remainingWidth; } else { - let minimapCharWidth: number; - if (pixelRatio >= 2) { - renderMinimap = minimapRenderCharacters ? RenderMinimap.Large : RenderMinimap.LargeBlocks; - minimapCharWidth = 2 / pixelRatio; - } else { - renderMinimap = minimapRenderCharacters ? RenderMinimap.Small : RenderMinimap.SmallBlocks; - minimapCharWidth = 1 / pixelRatio; - } + // The minimapScale is also the pixel width of each character. Adjust + // for the pixel ratio of the screen. + const minimapCharWidth = minimapScale / pixelRatio; + renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks; // Given: // (leaving 2px for the cursor to have space after the last character) @@ -1801,6 +1796,11 @@ export interface IEditorMinimapOptions { * Defaults to 120. */ maxColumn?: number; + + /** + * Relative size of the font in the minimap. Defaults to 1. + */ + scale?: number; } export type EditorMinimapOptions = Readonly<Required<IEditorMinimapOptions>>; @@ -1814,6 +1814,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap showSlider: 'mouseover', renderCharacters: true, maxColumn: 120, + scale: 1, }; super( EditorOption.minimap, 'minimap', defaults, @@ -1835,6 +1836,13 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap default: defaults.showSlider, description: nls.localize('minimap.showSlider', "Controls when the minimap slider is shown.") }, + 'editor.minimap.scale': { + type: 'number', + default: defaults.scale, + minimum: 1, + maximum: 3, + description: nls.localize('minimap.scale', "Scale of content drawn in the minimap.") + }, 'editor.minimap.renderCharacters': { type: 'boolean', default: defaults.renderCharacters, @@ -1859,6 +1867,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']), showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters), + scale: EditorIntOption.clampedInt(input.scale, 1, 1, 3), maxColumn: EditorIntOption.clampedInt(input.maxColumn, this.defaultValue.maxColumn, 1, 10000), }; } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 8ef13878743..9d227632149 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -403,10 +403,8 @@ export enum TextEditorCursorStyle { export enum RenderMinimap { None = 0, - Small = 1, - Large = 2, - SmallBlocks = 3, - LargeBlocks = 4 + Text = 1, + Blocks = 2 } export enum RenderLineNumbersType { diff --git a/src/vs/editor/common/view/minimapCharRenderer.ts b/src/vs/editor/common/view/minimapCharRenderer.ts deleted file mode 100644 index f47962e4c0f..00000000000 --- a/src/vs/editor/common/view/minimapCharRenderer.ts +++ /dev/null @@ -1,349 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter, Event } from 'vs/base/common/event'; -import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes'; - -export class MinimapTokensColorTracker { - private static _INSTANCE: MinimapTokensColorTracker | null = null; - public static getInstance(): MinimapTokensColorTracker { - if (!this._INSTANCE) { - this._INSTANCE = new MinimapTokensColorTracker(); - } - return this._INSTANCE; - } - - private _colors!: RGBA8[]; - private _backgroundIsLight!: boolean; - - private readonly _onDidChange = new Emitter<void>(); - public readonly onDidChange: Event<void> = this._onDidChange.event; - - private constructor() { - this._updateColorMap(); - TokenizationRegistry.onDidChange((e) => { - if (e.changedColorMap) { - this._updateColorMap(); - } - }); - } - - private _updateColorMap(): void { - const colorMap = TokenizationRegistry.getColorMap(); - if (!colorMap) { - this._colors = [RGBA8.Empty]; - this._backgroundIsLight = true; - return; - } - this._colors = [RGBA8.Empty]; - for (let colorId = 1; colorId < colorMap.length; colorId++) { - const source = colorMap[colorId].rgba; - // Use a VM friendly data-type - this._colors[colorId] = new RGBA8(source.r, source.g, source.b, Math.round(source.a * 255)); - } - let backgroundLuminosity = colorMap[ColorId.DefaultBackground].getRelativeLuminance(); - this._backgroundIsLight = (backgroundLuminosity >= 0.5); - this._onDidChange.fire(undefined); - } - - public getColor(colorId: ColorId): RGBA8 { - if (colorId < 1 || colorId >= this._colors.length) { - // background color (basically invisible) - colorId = ColorId.DefaultBackground; - } - return this._colors[colorId]; - } - - public backgroundIsLight(): boolean { - return this._backgroundIsLight; - } -} - -export const enum Constants { - START_CH_CODE = 32, // Space - END_CH_CODE = 126, // Tilde (~) - CHAR_COUNT = END_CH_CODE - START_CH_CODE + 1, - - SAMPLED_CHAR_HEIGHT = 16, - SAMPLED_CHAR_WIDTH = 10, - SAMPLED_HALF_CHAR_WIDTH = SAMPLED_CHAR_WIDTH / 2, - - x2_CHAR_HEIGHT = 4, - x2_CHAR_WIDTH = 2, - - x1_CHAR_HEIGHT = 2, - x1_CHAR_WIDTH = 1, - - RGBA_CHANNELS_CNT = 4, -} - -export class MinimapCharRenderer { - - _minimapCharRendererBrand: void; - - public readonly x2charData: Uint8ClampedArray; - public readonly x1charData: Uint8ClampedArray; - - public readonly x2charDataLight: Uint8ClampedArray; - public readonly x1charDataLight: Uint8ClampedArray; - - constructor(x2CharData: Uint8ClampedArray, x1CharData: Uint8ClampedArray) { - const x2ExpectedLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT; - if (x2CharData.length !== x2ExpectedLen) { - throw new Error('Invalid x2CharData'); - } - const x1ExpectedLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT; - if (x1CharData.length !== x1ExpectedLen) { - throw new Error('Invalid x1CharData'); - } - this.x2charData = x2CharData; - this.x1charData = x1CharData; - - this.x2charDataLight = MinimapCharRenderer.soften(x2CharData, 12 / 15); - this.x1charDataLight = MinimapCharRenderer.soften(x1CharData, 50 / 60); - } - - private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray { - let result = new Uint8ClampedArray(input.length); - for (let i = 0, len = input.length; i < len; i++) { - result[i] = input[i] * ratio; - } - return result; - } - - private static _getChIndex(chCode: number): number { - chCode -= Constants.START_CH_CODE; - if (chCode < 0) { - chCode += Constants.CHAR_COUNT; - } - return (chCode % Constants.CHAR_COUNT); - } - - public x2RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - const x2CharData = useLighterFont ? this.x2charDataLight : this.x2charData; - const chIndex = MinimapCharRenderer._getChIndex(chCode); - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const dest = target.data; - const sourceOffset = chIndex * Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH; - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - const c = x2CharData[sourceOffset] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 1] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x2CharData[sourceOffset + 2] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 3] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x2CharData[sourceOffset + 4] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 5] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x2CharData[sourceOffset + 6] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 7] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - } - - public x1RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - const x1CharData = useLighterFont ? this.x1charDataLight : this.x1charData; - const chIndex = MinimapCharRenderer._getChIndex(chCode); - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const dest = target.data; - const sourceOffset = chIndex * Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH; - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - const c = x1CharData[sourceOffset] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x1CharData[sourceOffset + 1] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - } - - public x2BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const c = 0.5; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const colorR = backgroundR + deltaR * c; - const colorG = backgroundG + deltaG * c; - const colorB = backgroundB + deltaB * c; - - const dest = target.data; - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - } - - public x1BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const c = 0.5; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const colorR = backgroundR + deltaR * c; - const colorG = backgroundG + deltaG * c; - const colorB = backgroundB + deltaB * c; - - const dest = target.data; - - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - } -} diff --git a/src/vs/editor/common/view/runtimeMinimapCharRenderer.ts b/src/vs/editor/common/view/runtimeMinimapCharRenderer.ts deleted file mode 100644 index 21f84e3fe98..00000000000 --- a/src/vs/editor/common/view/runtimeMinimapCharRenderer.ts +++ /dev/null @@ -1,985 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer'; - -function toUint8ClampedArrat(arr: number[]): Uint8ClampedArray { - let r = new Uint8ClampedArray(arr.length); - for (let i = 0, len = arr.length; i < len; i++) { - r[i] = arr[i]; - } - return r; -} - -let minimapCharRenderer: MinimapCharRenderer | null = null; -export function getOrCreateMinimapCharRenderer(): MinimapCharRenderer { - if (!minimapCharRenderer) { - let _x1Data = toUint8ClampedArrat(x1Data!); - x1Data = null; - - let _x2Data = toUint8ClampedArrat(x2Data!); - x2Data = null; - minimapCharRenderer = new MinimapCharRenderer(_x2Data, _x1Data); - } - return minimapCharRenderer; -} - -let x2Data: number[] | null = [ - - // - 0, 0, - 0, 0, - 0, 0, - 0, 0, - - // ! - 39, 14, - 39, 14, - 14, 5, - 29, 10, - - // " - 96, 96, - 29, 29, - 0, 0, - 0, 0, - - // # - 49, 113, - 195, 214, - 227, 166, - 135, 42, - - // $ - 40, 29, - 194, 38, - 75, 148, - 197, 187, - - // % - 145, 0, - 160, 61, - 75, 143, - 2, 183, - - // & - 138, 58, - 163, 6, - 177, 223, - 197, 227, - - // ' - 38, 13, - 11, 4, - 0, 0, - 0, 0, - - // ( - 10, 54, - 52, 8, - 62, 4, - 71, 122, - - // ) - 73, 2, - 19, 40, - 10, 50, - 155, 36, - - // * - 79, 70, - 145, 121, - 7, 5, - 0, 0, - - // + - 2, 1, - 36, 12, - 204, 166, - 16, 5, - - // , - 0, 0, - 0, 0, - 1, 0, - 154, 34, - - // - - 0, 0, - 0, 0, - 96, 83, - 0, 0, - - // . - 0, 0, - 0, 0, - 0, 0, - 46, 34, - - // / - 0, 82, - 2, 56, - 53, 3, - 146, 0, - - // 0 - 146, 119, - 152, 132, - 152, 131, - 145, 119, - - // 1 - 170, 42, - 15, 42, - 15, 42, - 172, 194, - - // 2 - 131, 132, - 0, 139, - 80, 28, - 227, 143, - - // 3 - 159, 135, - 15, 118, - 11, 126, - 171, 144, - - // 4 - 20, 124, - 88, 106, - 217, 196, - 0, 106, - - // 5 - 189, 92, - 168, 43, - 5, 130, - 164, 133, - - // 6 - 130, 115, - 183, 65, - 134, 120, - 141, 141, - - // 7 - 170, 196, - 2, 106, - 31, 32, - 105, 2, - - // 8 - 145, 130, - 116, 114, - 132, 135, - 138, 140, - - // 9 - 138, 113, - 147, 137, - 81, 183, - 129, 94, - - // : - 0, 0, - 21, 16, - 4, 3, - 46, 34, - - // ; - 0, 0, - 45, 34, - 1, 0, - 160, 49, - - // < - 0, 0, - 43, 143, - 203, 23, - 1, 76, - - // = - 0, 0, - 38, 28, - 131, 96, - 38, 28, - - // > - 0, 0, - 168, 31, - 29, 191, - 98, 0, - - // ? - 118, 139, - 5, 113, - 45, 13, - 37, 6, - - // @ - 97, 115, - 161, 179, - 204, 105, - 223, 224, - - // A - 83, 52, - 111, 100, - 184, 186, - 120, 132, - - // B - 212, 145, - 180, 139, - 174, 161, - 212, 182, - - // C - 104, 162, - 131, 0, - 131, 0, - 104, 161, - - // D - 219, 120, - 110, 116, - 110, 116, - 219, 120, - - // E - 207, 154, - 163, 40, - 147, 22, - 207, 154, - - // F - 202, 159, - 161, 47, - 145, 23, - 111, 0, - - // G - 139, 154, - 144, 30, - 144, 135, - 139, 187, - - // H - 110, 110, - 168, 161, - 150, 145, - 110, 110, - - // I - 185, 162, - 43, 16, - 43, 16, - 185, 162, - - // J - 73, 129, - 0, 110, - 0, 110, - 191, 87, - - // K - 149, 149, - 236, 48, - 195, 91, - 146, 149, - - // L - 146, 0, - 146, 0, - 146, 0, - 187, 173, - - // M - 200, 201, - 222, 215, - 172, 147, - 95, 95, - - // N - 193, 97, - 224, 129, - 159, 206, - 97, 192, - - // O - 155, 139, - 153, 115, - 153, 115, - 156, 140, - - // P - 189, 158, - 123, 136, - 190, 64, - 111, 0, - - // Q - 155, 139, - 153, 115, - 153, 114, - 156, 241, - - // R - 197, 148, - 150, 152, - 170, 116, - 110, 157, - - // S - 156, 128, - 169, 14, - 13, 159, - 158, 149, - - // T - 212, 189, - 43, 16, - 43, 16, - 43, 16, - - // U - 148, 110, - 148, 110, - 147, 109, - 182, 151, - - // V - 133, 121, - 106, 118, - 114, 103, - 89, 66, - - // W - 94, 94, - 211, 188, - 205, 207, - 139, 168, - - // X - 151, 152, - 87, 76, - 101, 79, - 151, 152, - - // Y - 130, 156, - 125, 116, - 47, 29, - 43, 16, - - // Z - 169, 228, - 11, 103, - 120, 6, - 230, 176, - - // [ - 55, 49, - 55, 6, - 55, 6, - 193, 102, - - // \ - 92, 0, - 71, 0, - 13, 30, - 0, 147, - - // ] - 63, 43, - 12, 43, - 12, 43, - 142, 152, - - // ^ - 71, 53, - 61, 61, - 0, 0, - 0, 0, - - // _ - 0, 0, - 0, 0, - 0, 0, - 158, 146, - - // ` - 25, 2, - 0, 0, - 0, 0, - 0, 0, - - // a - 0, 0, - 107, 130, - 170, 194, - 176, 188, - - // b - 109, 0, - 203, 159, - 113, 111, - 202, 158, - - // c - 0, 0, - 135, 135, - 114, 0, - 136, 135, - - // d - 0, 109, - 187, 190, - 148, 126, - 177, 187, - - // e - 0, 0, - 149, 130, - 218, 105, - 169, 135, - - // f - 37, 113, - 146, 113, - 49, 13, - 49, 13, - - // g - 0, 0, - 178, 195, - 147, 114, - 255, 255, - - // h - 109, 0, - 193, 149, - 110, 109, - 109, 109, - - // i - 12, 15, - 125, 41, - 33, 41, - 144, 188, - - // j - 1, 6, - 75, 53, - 10, 53, - 210, 161, - - // k - 110, 0, - 152, 148, - 210, 60, - 110, 156, - - // l - 213, 5, - 63, 5, - 63, 5, - 45, 111, - - // m - 0, 0, - 232, 172, - 190, 168, - 190, 169, - - // n - 0, 0, - 190, 144, - 109, 109, - 109, 109, - - // o - 0, 0, - 168, 140, - 148, 111, - 168, 140, - - // p - 0, 0, - 200, 151, - 113, 110, - 255, 158, - - // q - 0, 0, - 184, 188, - 147, 139, - 186, 255, - - // r - 0, 0, - 122, 130, - 111, 0, - 109, 0, - - // s - 0, 0, - 132, 69, - 109, 93, - 110, 136, - - // t - 51, 5, - 205, 103, - 61, 6, - 47, 106, - - // u - 0, 0, - 110, 109, - 110, 122, - 155, 179, - - // v - 0, 0, - 132, 120, - 113, 114, - 84, 63, - - // w - 0, 0, - 124, 108, - 202, 189, - 160, 174, - - // x - 0, 0, - 144, 142, - 79, 57, - 159, 146, - - // y - 0, 0, - 138, 138, - 119, 117, - 255, 69, - - // z - 0, 0, - 97, 198, - 47, 38, - 208, 84, - - // { - 23, 112, - 41, 14, - 157, 7, - 121, 192, - - // | - 35, 11, - 35, 11, - 35, 11, - 160, 61, - - // } - 129, 9, - 40, 19, - 20, 139, - 236, 44, - - // ~ - 0, 0, - 15, 3, - 97, 93, - 0, 0, - -]; - -let x1Data: number[] | null = [ - - // - 0, - 0, - - // ! - 23, - 12, - - // " - 53, - 0, - - // # - 130, - 127, - - // $ - 58, - 149, - - // % - 67, - 77, - - // & - 72, - 198, - - // ' - 13, - 0, - - // ( - 25, - 51, - - // ) - 25, - 49, - - // * - 94, - 2, - - // + - 8, - 64, - - // , - 0, - 24, - - // - - 0, - 21, - - // . - 0, - 9, - - // / - 19, - 27, - - // 0 - 126, - 126, - - // 1 - 51, - 80, - - // 2 - 72, - 105, - - // 3 - 87, - 98, - - // 4 - 73, - 93, - - // 5 - 106, - 85, - - // 6 - 111, - 123, - - // 7 - 87, - 30, - - // 8 - 116, - 126, - - // 9 - 123, - 110, - - // : - 4, - 16, - - // ; - 9, - 28, - - // < - 21, - 53, - - // = - 8, - 62, - - // > - 23, - 52, - - // ? - 73, - 21, - - // @ - 132, - 183, - - // A - 78, - 142, - - // B - 168, - 175, - - // C - 70, - 70, - - // D - 128, - 128, - - // E - 123, - 110, - - // F - 125, - 43, - - // G - 100, - 139, - - // H - 125, - 119, - - // I - 78, - 78, - - // J - 54, - 77, - - // K - 139, - 139, - - // L - 33, - 87, - - // M - 201, - 117, - - // N - 162, - 149, - - // O - 130, - 130, - - // P - 138, - 60, - - // Q - 130, - 172, - - // R - 149, - 127, - - // S - 95, - 98, - - // T - 95, - 25, - - // U - 118, - 135, - - // V - 110, - 85, - - // W - 147, - 175, - - // X - 105, - 110, - - // Y - 121, - 30, - - // Z - 101, - 113, - - // [ - 34, - 68, - - // \ - 20, - 26, - - // ] - 34, - 68, - - // ^ - 56, - 0, - - // _ - 0, - 44, - - // ` - 3, - 0, - - // a - 27, - 175, - - // b - 80, - 133, - - // c - 31, - 66, - - // d - 85, - 147, - - // e - 32, - 150, - - // f - 90, - 25, - - // g - 45, - 230, - - // h - 77, - 101, - - // i - 36, - 83, - - // j - 22, - 84, - - // k - 71, - 118, - - // l - 44, - 44, - - // m - 52, - 172, - - // n - 38, - 101, - - // o - 35, - 130, - - // p - 40, - 197, - - // q - 43, - 197, - - // r - 29, - 26, - - // s - 23, - 103, - - // t - 67, - 44, - - // u - 25, - 129, - - // v - 29, - 85, - - // w - 27, - 177, - - // x - 33, - 97, - - // y - 32, - 145, - - // z - 33, - 77, - - // { - 38, - 96, - - // | - 20, - 55, - - // } - 36, - 95, - - // ~ - 2, - 22, - -]; diff --git a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts new file mode 100644 index 00000000000..39eb06d774c --- /dev/null +++ b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { RGBA8 } from 'vs/editor/common/core/rgba'; +import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes'; + +export class MinimapTokensColorTracker { + private static _INSTANCE: MinimapTokensColorTracker | null = null; + public static getInstance(): MinimapTokensColorTracker { + if (!this._INSTANCE) { + this._INSTANCE = new MinimapTokensColorTracker(); + } + return this._INSTANCE; + } + + private _colors!: RGBA8[]; + private _backgroundIsLight!: boolean; + + private readonly _onDidChange = new Emitter<void>(); + public readonly onDidChange: Event<void> = this._onDidChange.event; + + private constructor() { + this._updateColorMap(); + TokenizationRegistry.onDidChange(e => { + if (e.changedColorMap) { + this._updateColorMap(); + } + }); + } + + private _updateColorMap(): void { + const colorMap = TokenizationRegistry.getColorMap(); + if (!colorMap) { + this._colors = [RGBA8.Empty]; + this._backgroundIsLight = true; + return; + } + this._colors = [RGBA8.Empty]; + for (let colorId = 1; colorId < colorMap.length; colorId++) { + const source = colorMap[colorId].rgba; + // Use a VM friendly data-type + this._colors[colorId] = new RGBA8(source.r, source.g, source.b, Math.round(source.a * 255)); + } + let backgroundLuminosity = colorMap[ColorId.DefaultBackground].getRelativeLuminance(); + this._backgroundIsLight = backgroundLuminosity >= 0.5; + this._onDidChange.fire(undefined); + } + + public getColor(colorId: ColorId): RGBA8 { + if (colorId < 1 || colorId >= this._colors.length) { + // background color (basically invisible) + colorId = ColorId.DefaultBackground; + } + return this._colors[colorId]; + } + + public backgroundIsLight(): boolean { + return this._backgroundIsLight; + } +} diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 544a115ecf1..7b11c6ef57d 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -15,7 +15,7 @@ import { ModelDecorationOverviewRulerOptions, ModelDecorationMinimapOptions } fr import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; import { ColorId, LanguageId, TokenizationRegistry } from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; -import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer'; +import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; diff --git a/src/vs/editor/test/common/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index f56ebe2bca6..f45f5fa1162 100644 --- a/src/vs/editor/test/common/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -5,9 +5,8 @@ import * as assert from 'assert'; import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { Constants } from 'vs/editor/common/view/minimapCharRenderer'; -import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer'; -import { MinimapCharRendererFactory } from 'vs/editor/test/common/view/minimapCharRendererFactory'; +import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; suite('MinimapCharRenderer', () => { @@ -75,11 +74,11 @@ suite('MinimapCharRenderer', () => { test('letter d @ 2x', () => { setSampleData('d'.charCodeAt(0), sampleD); - let renderer = MinimapCharRendererFactory.create(sampleData!); + let renderer = MinimapCharRendererFactory.createFromSampleData(sampleData!, 2); let background = new RGBA8(0, 0, 0, 255); let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x2_CHAR_WIDTH, Constants.x2_CHAR_HEIGHT); + let imageData = createFakeImageData(Constants.BASE_CHAR_WIDTH * 2, Constants.BASE_CHAR_HEIGHT * 2); // set the background color for (let i = 0, len = imageData.data.length / 4; i < len; i++) { imageData.data[4 * i + 0] = background.r; @@ -87,55 +86,28 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 2] = background.b; imageData.data[4 * i + 3] = 255; } - renderer.x2RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { actual[i] = imageData.data[i]; } - assert.deepEqual(actual, [ - 0x00, 0x00, 0x00, 0xFF, 0x6D, 0x6D, 0x6D, 0xFF, - 0xBB, 0xBB, 0xBB, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, - 0x94, 0x94, 0x94, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, - 0xB1, 0xB1, 0xB1, 0xFF, 0xBB, 0xBB, 0xBB, 0xFF, - ]); - }); - test('letter d @ 2x at runtime', () => { - let renderer = getOrCreateMinimapCharRenderer(); - - let background = new RGBA8(0, 0, 0, 255); - let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x2_CHAR_WIDTH, Constants.x2_CHAR_HEIGHT); - // set the background color - for (let i = 0, len = imageData.data.length / 4; i < len; i++) { - imageData.data[4 * i + 0] = background.r; - imageData.data[4 * i + 1] = background.g; - imageData.data[4 * i + 2] = background.b; - imageData.data[4 * i + 3] = 255; - } - - renderer.x2RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); - - let actual: number[] = []; - for (let i = 0; i < imageData.data.length; i++) { - actual[i] = imageData.data[i]; - } assert.deepEqual(actual, [ - 0x00, 0x00, 0x00, 0xFF, 0x6D, 0x6D, 0x6D, 0xFF, - 0xBB, 0xBB, 0xBB, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, - 0x94, 0x94, 0x94, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, - 0xB1, 0xB1, 0xB1, 0xFF, 0xBB, 0xBB, 0xBB, 0xFF, + 0x2E, 0x2E, 0x2E, 0xFF, 0xAD, 0xAD, 0xAD, 0xFF, + 0xC6, 0xC6, 0xC6, 0xFF, 0xC8, 0xC8, 0xC8, 0xFF, + 0xC1, 0xC1, 0xC1, 0xFF, 0xCC, 0xCC, 0xCC, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, ]); }); test('letter d @ 1x', () => { setSampleData('d'.charCodeAt(0), sampleD); - let renderer = MinimapCharRendererFactory.create(sampleData!); + let renderer = MinimapCharRendererFactory.createFromSampleData(sampleData!, 1); let background = new RGBA8(0, 0, 0, 255); let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x1_CHAR_WIDTH, Constants.x1_CHAR_HEIGHT); + let imageData = createFakeImageData(Constants.BASE_CHAR_WIDTH, Constants.BASE_CHAR_HEIGHT); // set the background color for (let i = 0, len = imageData.data.length / 4; i < len; i++) { imageData.data[4 * i + 0] = background.r; @@ -144,42 +116,17 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 3] = 255; } - renderer.x1RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { actual[i] = imageData.data[i]; } - assert.deepEqual(actual, [ - 0x55, 0x55, 0x55, 0xFF, - 0x93, 0x93, 0x93, 0xFF, - ]); - }); - test('letter d @ 1x at runtime', () => { - let renderer = getOrCreateMinimapCharRenderer(); - - let background = new RGBA8(0, 0, 0, 255); - let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x1_CHAR_WIDTH, Constants.x1_CHAR_HEIGHT); - // set the background color - for (let i = 0, len = imageData.data.length / 4; i < len; i++) { - imageData.data[4 * i + 0] = background.r; - imageData.data[4 * i + 1] = background.g; - imageData.data[4 * i + 2] = background.b; - imageData.data[4 * i + 3] = 255; - } - - renderer.x1RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); - - let actual: number[] = []; - for (let i = 0; i < imageData.data.length; i++) { - actual[i] = imageData.data[i]; - } assert.deepEqual(actual, [ - 0x55, 0x55, 0x55, 0xFF, - 0x93, 0x93, 0x93, 0xFF, + 0xCB, 0xCB, 0xCB, 0xFF, + 0x82, 0x82, 0x82, 0xFF, ]); }); -});
\ No newline at end of file +}); diff --git a/src/vs/editor/test/browser/view/minimapFontCreator.ts b/src/vs/editor/test/browser/view/minimapFontCreator.ts index afd9b17e328..b064114f722 100644 --- a/src/vs/editor/test/browser/view/minimapFontCreator.ts +++ b/src/vs/editor/test/browser/view/minimapFontCreator.ts @@ -4,31 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { Constants, MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer'; -import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer'; -import { MinimapCharRendererFactory } from 'vs/editor/test/common/view/minimapCharRendererFactory'; +import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; +import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; -let canvas = <HTMLCanvasElement>document.getElementById('my-canvas'); -let ctx = canvas.getContext('2d')!; - -canvas.style.height = 100 + 'px'; -canvas.height = 100; - -canvas.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH; -canvas.style.width = (Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH) + 'px'; - -ctx.fillStyle = '#ffffff'; -ctx.font = 'bold 16px monospace'; -for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - ctx.fillText(String.fromCharCode(chCode), (chCode - Constants.START_CH_CODE) * Constants.SAMPLED_CHAR_WIDTH, Constants.SAMPLED_CHAR_HEIGHT); -} - -let sampleData = ctx.getImageData(0, 4, Constants.SAMPLED_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.SAMPLED_CHAR_HEIGHT); -let minimapCharRenderer = MinimapCharRendererFactory.create(sampleData.data); +let sampleData = MinimapCharRendererFactory.createSampleData('monospace'); +let minimapCharRenderer1x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 1); +let minimapCharRenderer2x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 2); +let minimapCharRenderer4x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 4); +let minimapCharRenderer6x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 6); renderImageData(sampleData, 10, 100); -renderMinimapCharRenderer(minimapCharRenderer, 400); -renderMinimapCharRenderer(getOrCreateMinimapCharRenderer(), 600); +renderMinimapCharRenderer(minimapCharRenderer1x, 400, 1); +renderMinimapCharRenderer(minimapCharRenderer2x, 500, 2); +renderMinimapCharRenderer(minimapCharRenderer4x, 600, 4); +renderMinimapCharRenderer(minimapCharRenderer6x, 750, 8); function createFakeImageData(width: number, height: number): ImageData { return { @@ -38,13 +28,15 @@ function createFakeImageData(width: number, height: number): ImageData { }; } -function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: number): void { - +function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: number, scale: number): void { let background = new RGBA8(0, 0, 0, 255); let color = new RGBA8(255, 255, 255, 255); { - let x2 = createFakeImageData(Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x2_CHAR_HEIGHT); + let x2 = createFakeImageData( + Constants.BASE_CHAR_WIDTH * scale * Constants.CHAR_COUNT, + Constants.BASE_CHAR_HEIGHT * scale + ); // set the background color for (let i = 0, len = x2.data.length / 4; i < len; i++) { x2.data[4 * i + 0] = background.r; @@ -54,67 +46,49 @@ function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: } let dx = 0; for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - minimapCharRenderer.x2RenderChar(x2, dx, 0, chCode, color, background, false); - dx += Constants.x2_CHAR_WIDTH; + minimapCharRenderer.renderChar(x2, dx, 0, chCode, color, background, false); + dx += Constants.BASE_CHAR_WIDTH * scale; } renderImageData(x2, 10, y); } - { - let x1 = createFakeImageData(Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x1_CHAR_HEIGHT); - // set the background color - for (let i = 0, len = x1.data.length / 4; i < len; i++) { - x1.data[4 * i + 0] = background.r; - x1.data[4 * i + 1] = background.g; - x1.data[4 * i + 2] = background.b; - x1.data[4 * i + 3] = 255; - } - let dx = 0; - for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - minimapCharRenderer.x1RenderChar(x1, dx, 0, chCode, color, background, false); - dx += Constants.x1_CHAR_WIDTH; - } - renderImageData(x1, 10, y + 100); - } } (function () { - let r = 'let x2Data = [', offset = 0; + let r = 'let x2Data = [', + offset = 0; for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { let charCode = charIndex + Constants.START_CH_CODE; r += '\n\n// ' + String.fromCharCode(charCode); - for (let i = 0; i < Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH; i++) { + for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * 2; i++) { if (i % 2 === 0) { r += '\n'; } - r += minimapCharRenderer.x2charData[offset] + ','; + r += (minimapCharRenderer2x as any).charDataNormal[offset] + ','; offset++; } - } r += '\n\n]'; console.log(r); })(); (function () { - let r = 'let x1Data = [', offset = 0; + let r = 'let x1Data = [', + offset = 0; for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { let charCode = charIndex + Constants.START_CH_CODE; r += '\n\n// ' + String.fromCharCode(charCode); - for (let i = 0; i < Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH; i++) { + for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * Constants.BASE_CHAR_WIDTH; i++) { r += '\n'; - r += minimapCharRenderer.x1charData[offset] + ','; + r += (minimapCharRenderer1x as any).charDataNormal[offset] + ','; offset++; } - } r += '\n\n]'; console.log(r); })(); - - function renderImageData(imageData: ImageData, left: number, top: number): void { let output = ''; let offset = 0; @@ -127,7 +101,8 @@ function renderImageData(imageData: ImageData, left: number, top: number): void let A = imageData.data[offset + 3]; offset += 4; - output += `<div style="position:absolute;top:${PX_SIZE * i}px;left:${PX_SIZE * j}px;width:${PX_SIZE}px;height:${PX_SIZE}px;background:rgba(${R},${G},${B},${A / 256})"></div>`; + output += `<div style="position:absolute;top:${PX_SIZE * i}px;left:${PX_SIZE * + j}px;width:${PX_SIZE}px;height:${PX_SIZE}px;background:rgba(${R},${G},${B},${A / 256})"></div>`; } } @@ -135,8 +110,8 @@ function renderImageData(imageData: ImageData, left: number, top: number): void domNode.style.position = 'absolute'; domNode.style.top = top + 'px'; domNode.style.left = left + 'px'; - domNode.style.width = (imageData.width * PX_SIZE) + 'px'; - domNode.style.height = (imageData.height * PX_SIZE) + 'px'; + domNode.style.width = imageData.width * PX_SIZE + 'px'; + domNode.style.height = imageData.height * PX_SIZE + 'px'; domNode.style.border = '1px solid #ccc'; domNode.style.background = '#000000'; domNode.innerHTML = output; diff --git a/src/vs/editor/test/common/view/minimapCharRendererFactory.ts b/src/vs/editor/test/common/view/minimapCharRendererFactory.ts deleted file mode 100644 index 43989a1e1fa..00000000000 --- a/src/vs/editor/test/common/view/minimapCharRendererFactory.ts +++ /dev/null @@ -1,172 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Constants, MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer'; - -const enum InternalConstants { - CA_CHANNELS_CNT = 2, -} - -export class MinimapCharRendererFactory { - - public static create(source: Uint8ClampedArray): MinimapCharRenderer { - const expectedLength = (Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT); - if (source.length !== expectedLength) { - throw new Error('Unexpected source in MinimapCharRenderer'); - } - - let x2CharData = this.toGrayscale(MinimapCharRendererFactory._downsample2x(source)); - let x1CharData = this.toGrayscale(MinimapCharRendererFactory._downsample1x(source)); - return new MinimapCharRenderer(x2CharData, x1CharData); - } - - private static toGrayscale(charData: Uint8ClampedArray): Uint8ClampedArray { - let newLength = charData.length / 2; - let result = new Uint8ClampedArray(newLength); - let sourceOffset = 0; - for (let i = 0; i < newLength; i++) { - let color = charData[sourceOffset]; - let alpha = charData[sourceOffset + 1]; - let newColor = Math.round((color * alpha) / 255); - result[i] = newColor; - sourceOffset += 2; - } - return result; - } - - private static _extractSampledChar(source: Uint8ClampedArray, charIndex: number, dest: Uint8ClampedArray) { - let destOffset = 0; - for (let i = 0; i < Constants.SAMPLED_CHAR_HEIGHT; i++) { - let sourceOffset = ( - Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT * i - + Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * charIndex - ); - for (let j = 0; j < Constants.SAMPLED_CHAR_WIDTH; j++) { - for (let c = 0; c < Constants.RGBA_CHANNELS_CNT; c++) { - dest[destOffset] = source[sourceOffset]; - sourceOffset++; - destOffset++; - } - } - } - } - - private static _downsample2xChar(source: Uint8ClampedArray, dest: Uint8ClampedArray): void { - // chars are 2 x 4px (width x height) - const resultLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT; - const result = new Uint16Array(resultLen); - for (let i = 0; i < resultLen; i++) { - result[i] = 0; - } - - let inputOffset = 0, globalOutputOffset = 0; - for (let i = 0; i < Constants.SAMPLED_CHAR_HEIGHT; i++) { - - let outputOffset = globalOutputOffset; - - let color = 0; - let alpha = 0; - for (let j = 0; j < Constants.SAMPLED_HALF_CHAR_WIDTH; j++) { - color += source[inputOffset]; // R - alpha += source[inputOffset + 3]; // A - inputOffset += Constants.RGBA_CHANNELS_CNT; - } - result[outputOffset] += color; - result[outputOffset + 1] += alpha; - outputOffset += InternalConstants.CA_CHANNELS_CNT; - - color = 0; - alpha = 0; - for (let j = 0; j < Constants.SAMPLED_HALF_CHAR_WIDTH; j++) { - color += source[inputOffset]; // R - alpha += source[inputOffset + 3]; // A - inputOffset += Constants.RGBA_CHANNELS_CNT; - } - result[outputOffset] += color; - result[outputOffset + 1] += alpha; - outputOffset += InternalConstants.CA_CHANNELS_CNT; - - if (i === 2 || i === 5 || i === 8) { - globalOutputOffset = outputOffset; - } - } - - for (let i = 0; i < resultLen; i++) { - dest[i] = result[i] / 12; // 15 it should be - } - } - - private static _downsample2x(data: Uint8ClampedArray): Uint8ClampedArray { - const resultLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * Constants.CHAR_COUNT; - const result = new Uint8ClampedArray(resultLen); - - const sampledChar = new Uint8ClampedArray(Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT); - const downsampledChar = new Uint8ClampedArray(Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT); - - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - this._extractSampledChar(data, charIndex, sampledChar); - this._downsample2xChar(sampledChar, downsampledChar); - let resultOffset = (Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * charIndex); - for (let i = 0; i < downsampledChar.length; i++) { - result[resultOffset + i] = downsampledChar[i]; - } - } - - return result; - } - - private static _downsample1xChar(source: Uint8ClampedArray, dest: Uint8ClampedArray): void { - // chars are 1 x 2px (width x height) - const resultLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT; - const result = new Uint16Array(resultLen); - for (let i = 0; i < resultLen; i++) { - result[i] = 0; - } - - let inputOffset = 0, globalOutputOffset = 0; - for (let i = 0; i < Constants.SAMPLED_CHAR_HEIGHT; i++) { - - let outputOffset = globalOutputOffset; - - let color = 0; - let alpha = 0; - for (let j = 0; j < Constants.SAMPLED_CHAR_WIDTH; j++) { - color += source[inputOffset]; // R - alpha += source[inputOffset + 3]; // A - inputOffset += Constants.RGBA_CHANNELS_CNT; - } - result[outputOffset] += color; - result[outputOffset + 1] += alpha; - outputOffset += InternalConstants.CA_CHANNELS_CNT; - - if (i === 5) { - globalOutputOffset = outputOffset; - } - } - - for (let i = 0; i < resultLen; i++) { - dest[i] = result[i] / 50; // 60 it should be - } - } - - private static _downsample1x(data: Uint8ClampedArray): Uint8ClampedArray { - const resultLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * Constants.CHAR_COUNT; - const result = new Uint8ClampedArray(resultLen); - - const sampledChar = new Uint8ClampedArray(Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT); - const downsampledChar = new Uint8ClampedArray(Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT); - - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - this._extractSampledChar(data, charIndex, sampledChar); - this._downsample1xChar(sampledChar, downsampledChar); - let resultOffset = (Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * charIndex); - for (let i = 0; i < downsampledChar.length; i++) { - result[resultOffset + i] = downsampledChar[i]; - } - } - - return result; - } -} diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 793ab31cf7f..4d6e0a4fe71 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -48,7 +48,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { side: input.minimapSide, renderCharacters: input.minimapRenderCharacters, maxColumn: input.minimapMaxColumn, - showSlider: 'mouseover' + showSlider: 'mouseover', + scale: 1, }; options._write(EditorOption.minimap, minimapOptions); const scrollbarOptions: InternalEditorScrollbarOptions = { @@ -704,7 +705,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 901, contentHeight: 800, - renderMinimap: RenderMinimap.Small, + renderMinimap: RenderMinimap.Text, minimapLeft: 911, minimapWidth: 89, viewportColumn: 89, @@ -762,7 +763,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 901, contentHeight: 800, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 911, minimapWidth: 89, viewportColumn: 89, @@ -820,7 +821,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 943, contentHeight: 800, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 953, minimapWidth: 47, viewportColumn: 94, @@ -878,7 +879,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 943, contentHeight: 800, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 0, minimapWidth: 47, viewportColumn: 94, @@ -936,7 +937,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 1026, contentHeight: 422, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 1104, minimapWidth: 83, viewportColumn: 83, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 70aed6e2f7d..eb697d93760 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3100,10 +3100,8 @@ declare namespace monaco.editor { export enum RenderMinimap { None = 0, - Small = 1, - Large = 2, - SmallBlocks = 3, - LargeBlocks = 4 + Text = 1, + Blocks = 2 } /** @@ -3238,6 +3236,10 @@ declare namespace monaco.editor { * Defaults to 120. */ maxColumn?: number; + /** + * Relative size of the font in the minimap. Defaults to 1. + */ + scale?: number; } export type EditorMinimapOptions = Readonly<Required<IEditorMinimapOptions>>; |