minimap.ts 19.6 KB
Newer Older
A
Alex Dima 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8
import 'vs/css!./minimap';
A
Alex Dima 已提交
9
import { ViewPart } from 'vs/editor/browser/view/viewPart';
A
Alex Dima 已提交
10 11
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { IRenderingContext, IRestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
A
Alex Dima 已提交
12
import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer';
A
Alex Dima 已提交
13
import * as browser from 'vs/base/browser/browser';
A
Alex Dima 已提交
14
import { MinimapCharRenderer, ParsedColor, MinimapTokensColorTracker, Constants } from 'vs/editor/common/view/minimapCharRenderer';
A
Alex Dima 已提交
15
import * as editorCommon from 'vs/editor/common/editorCommon';
16
import { CharCode } from 'vs/base/common/charCode';
A
Alex Dima 已提交
17
import { IViewLayout, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
A
Alex Dima 已提交
18
import { ColorId } from 'vs/editor/common/modes';
19 20
import { FastDomNode, createFastDomNode } from 'vs/base/browser/styleMutator';
import { IDisposable } from 'vs/base/common/lifecycle';
21
import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar';
A
Alex Dima 已提交
22
import { RenderedLinesCollection, ILine } from 'vs/editor/browser/view/viewLayer';
23

24 25 26 27 28 29 30 31 32 33
const enum RenderMinimap {
	None = 0,
	Small = 1,
	Large = 2
}

class MinimapOptions {

	public readonly renderMinimap: RenderMinimap;

34 35
	public readonly pixelRatio: number;

36 37
	public readonly lineHeight: number;

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
	/**
	 * container dom node width (in CSS px)
	 */
	public readonly minimapWidth: number;
	/**
	 * container dom node height (in CSS px)
	 */
	public readonly minimapHeight: number;

	/**
	 * canvas backing store width (in device px)
	 */
	public readonly canvasInnerWidth: number;
	/**
	 * canvas backing store height (in device px)
	 */
	public readonly canvasInnerHeight: number;

	/**
	 * canvas width (in CSS px)
	 */
	public readonly canvasOuterWidth: number;
	/**
	 * canvas height (in CSS px)
	 */
	public readonly canvasOuterHeight: number;

	constructor(configuration: editorCommon.IConfiguration) {
		const pixelRatio = browser.getPixelRatio();
		const layoutInfo = configuration.editor.layoutInfo;

		this.renderMinimap = layoutInfo.renderMinimap | 0;
70
		this.pixelRatio = pixelRatio;
71
		this.lineHeight = configuration.editor.lineHeight;
72 73 74 75 76 77 78 79 80 81 82 83
		this.minimapWidth = layoutInfo.minimapWidth;
		this.minimapHeight = layoutInfo.height;

		this.canvasInnerWidth = Math.floor(pixelRatio * this.minimapWidth);
		this.canvasInnerHeight = Math.floor(pixelRatio * this.minimapHeight);

		this.canvasOuterWidth = this.canvasInnerWidth / pixelRatio;
		this.canvasOuterHeight = this.canvasInnerHeight / pixelRatio;
	}

	public equals(other: MinimapOptions): boolean {
		return (this.renderMinimap === other.renderMinimap
84
			&& this.pixelRatio === other.pixelRatio
85
			&& this.lineHeight === other.lineHeight
86 87 88 89 90 91 92 93 94 95
			&& this.minimapWidth === other.minimapWidth
			&& this.minimapHeight === other.minimapHeight
			&& this.canvasInnerWidth === other.canvasInnerWidth
			&& this.canvasInnerHeight === other.canvasInnerHeight
			&& this.canvasOuterWidth === other.canvasOuterWidth
			&& this.canvasOuterHeight === other.canvasOuterHeight
		);
	}
}

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
class MinimapLayout {

	/**
	 * slider dom node top (in CSS px)
	 */
	public readonly sliderTop: number;
	/**
	 * slider dom node height (in CSS px)
	 */
	public readonly sliderHeight: number;

	/**
	 * minimap render start line number.
	 */
	public readonly startLineNumber: number;
	/**
	 * minimap render end line number.
	 */
	public readonly endLineNumber: number;

	constructor(
A
Alex Dima 已提交
117
		lastRenderData: RenderData,
118 119 120
		options: MinimapOptions,
		viewportStartLineNumber: number,
		viewportEndLineNumber: number,
121
		viewportHeight: number,
122 123
		lineCount: number,
		scrollbarSliderCenter: number
124 125 126
	) {
		const pixelRatio = options.pixelRatio;
		const minimapLineHeight = (options.renderMinimap === RenderMinimap.Large ? Constants.x2_CHAR_HEIGHT : Constants.x1_CHAR_HEIGHT);
127
		const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight);
128
		const lineHeight = options.lineHeight;
129

A
Alex Dima 已提交
130 131 132 133 134 135 136 137 138 139
		// Sometimes, the number of rendered lines varies for a constant viewport height.
		// The reason is that only parts of the viewportStartLineNumber or viewportEndLineNumber are visible.
		// This leads to an apparent tremor in the minimap's slider height.
		// We try here to compensate, making the slider slightly incorrect in these cases, but more pleasing to the eye.
		let viewportLineCount = viewportEndLineNumber - viewportStartLineNumber + 1;
		const expectedViewportLineCount = Math.round(viewportHeight / lineHeight);
		if (viewportLineCount > expectedViewportLineCount) {
			viewportLineCount = expectedViewportLineCount;
		}

140 141 142 143 144
		if (minimapLinesFitting >= lineCount) {
			// All lines fit in the minimap => no minimap scrolling
			this.startLineNumber = 1;
			this.endLineNumber = lineCount;
		} else {
A
Alex Dima 已提交
145
			// The desire is to align (centers) the minimap's slider with the scrollbar's slider
146

A
Alex Dima 已提交
147
			// For a resolved this.startLineNumber, we can compute the minimap's slider's center with the following formula:
148
			// scrollbarSliderCenter = (viewportStartLineNumber - this.startLineNumber + viewportLineCount/2) * minimapLineHeight / pixelRatio;
A
Alex Dima 已提交
149
			// =>
150 151 152 153
			// scrollbarSliderCenter = (viewportStartLineNumber - this.startLineNumber + viewportLineCount/2) * minimapLineHeight / pixelRatio;
			// scrollbarSliderCenter * pixelRatio / minimapLineHeight = viewportStartLineNumber - this.startLineNumber + viewportLineCount/2
			// this.startLineNumber = viewportStartLineNumber + viewportLineCount/2 - scrollbarSliderCenter * pixelRatio / minimapLineHeight
			let desiredStartLineNumber = Math.floor(viewportStartLineNumber + viewportLineCount / 2 - scrollbarSliderCenter * pixelRatio / minimapLineHeight);
A
Alex Dima 已提交
154 155
			let desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;

A
Alex Dima 已提交
156 157 158
			// Aligning the slider's centers can result (correctly) in tremor.
			// i.e. scrolling down might result in the startLineNumber going up.
			// Avoid this tremor by being consistent w.r.t. the previous computed result
A
Alex Dima 已提交
159
			if (lastRenderData) {
A
Alex Dima 已提交
160 161
				const lastLayoutDecision = lastRenderData.renderedLayout;
				if (lastLayoutDecision.viewportStartLineNumber <= viewportStartLineNumber) {
A
Alex Dima 已提交
162
					// going down => make sure we don't go above our previous decision
A
Alex Dima 已提交
163 164
					if (desiredStartLineNumber < lastLayoutDecision.startLineNumber) {
						desiredStartLineNumber = lastLayoutDecision.startLineNumber;
A
Alex Dima 已提交
165 166 167
						desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;
					}
				}
A
Alex Dima 已提交
168
				if (lastLayoutDecision.viewportStartLineNumber >= viewportStartLineNumber) {
A
Alex Dima 已提交
169
					// going up => make sure we don't go below our previous decision
A
Alex Dima 已提交
170 171
					if (desiredEndLineNumber > lastLayoutDecision.endLineNumber) {
						desiredEndLineNumber = lastLayoutDecision.endLineNumber;
A
Alex Dima 已提交
172 173 174 175 176
						desiredStartLineNumber = desiredEndLineNumber - minimapLinesFitting + 1;
					}
				}
			}

A
Alex Dima 已提交
177 178 179 180 181 182 183 184 185 186 187 188
			// Aligning the slider's centers is a very good thing, but this would make
			// the minimap never scroll all the way to the top or to the bottom of the file.
			// We therefore check that the viewport lines are in the minimap viewport.

			// (a) validate on start line number
			if (desiredStartLineNumber < 1) {
				// must start after 1
				desiredStartLineNumber = 1;
				desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;
			}
			if (desiredStartLineNumber > viewportStartLineNumber) {
				// must contain the viewport's start line number
189
				desiredStartLineNumber = viewportStartLineNumber;
A
Alex Dima 已提交
190 191 192 193 194 195 196 197
				desiredEndLineNumber = desiredStartLineNumber + minimapLinesFitting - 1;
			}

			// (b) validate on end line number
			if (desiredEndLineNumber > lineCount) {
				// must end before line count
				desiredEndLineNumber = lineCount;
				desiredStartLineNumber = desiredEndLineNumber - minimapLinesFitting + 1;
198
			}
A
Alex Dima 已提交
199 200 201 202
			if (desiredEndLineNumber < viewportEndLineNumber) {
				// must contain the viewport's end line number
				desiredEndLineNumber = viewportEndLineNumber;
				desiredStartLineNumber = desiredEndLineNumber - minimapLinesFitting + 1;
203
			}
204

205
			this.startLineNumber = desiredStartLineNumber;
A
Alex Dima 已提交
206
			this.endLineNumber = desiredEndLineNumber;
207
		}
208 209

		this.sliderTop = Math.floor((viewportStartLineNumber - this.startLineNumber) * minimapLineHeight / pixelRatio);
A
Alex Dima 已提交
210
		this.sliderHeight = Math.floor(viewportLineCount * minimapLineHeight / pixelRatio);
A
Alex Dima 已提交
211 212 213
	}
}

A
Alex Dima 已提交
214
class RenderedLayout {
A
Alex Dima 已提交
215 216 217 218 219 220 221 222 223 224
	/**
	 * editor viewport start line number.
	 */
	public readonly viewportStartLineNumber: number;
	/**
	 * editor viewport end line number.
	 */
	public readonly viewportEndLineNumber: number;

	/**
A
Alex Dima 已提交
225
	 * minimap rendered start line number.
A
Alex Dima 已提交
226 227 228
	 */
	public readonly startLineNumber: number;
	/**
A
Alex Dima 已提交
229
	 * minimap rendered end line number.
A
Alex Dima 已提交
230 231 232 233 234 235 236 237 238
	 */
	public readonly endLineNumber: number;

	constructor(
		viewportStartLineNumber: number,
		viewportEndLineNumber: number,
		startLineNumber: number,
		endLineNumber: number
	) {
A
Alex Dima 已提交
239
		this.viewportStartLineNumber = viewportStartLineNumber;
A
Alex Dima 已提交
240 241 242
		this.viewportEndLineNumber = viewportEndLineNumber;
		this.startLineNumber = startLineNumber;
		this.endLineNumber = endLineNumber;
243 244 245
	}
}

A
Alex Dima 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
class MinimapLine implements ILine {

	public static INVALID = new MinimapLine(-1);

	private _dy: number;

	constructor(dy: number) {
		this._dy = dy;
	}

	public onContentChanged(): void {
		this._dy = -1;
	}

	public onTokensChanged(): void {
		this._dy = -1;
	}
}

class RenderData {
	/**
	 * last rendered layout.
	 */
	public readonly renderedLayout: RenderedLayout;
	private readonly _imageData: ImageData;
	private readonly _renderedLines: RenderedLinesCollection<MinimapLine>;

	constructor(
		renderedLayout: RenderedLayout,
		imageData: ImageData,
		lines: MinimapLine[]
	) {
		this.renderedLayout = renderedLayout;
		this._imageData = imageData;
		this._renderedLines = new RenderedLinesCollection(
			() => MinimapLine.INVALID
		);
		this._renderedLines._set(renderedLayout.startLineNumber, lines);
	}

	public onModelLinesDeleted(e: editorCommon.IViewLinesDeletedEvent): void {
		this._renderedLines.onModelLinesDeleted(e.fromLineNumber, e.toLineNumber);
	}
	public onModelLineChanged(e: editorCommon.IViewLineChangedEvent): boolean {
		return this._renderedLines.onModelLineChanged(e.lineNumber);
	}
	public onModelLinesInserted(e: editorCommon.IViewLinesInsertedEvent): void {
		this._renderedLines.onModelLinesInserted(e.fromLineNumber, e.toLineNumber);
	}
	public onModelTokensChanged(e: editorCommon.IViewTokensChangedEvent): boolean {
		return this._renderedLines.onModelTokensChanged(e.ranges);
	}
}

A
Alex Dima 已提交
300 301
export class Minimap extends ViewPart {

302
	private readonly _viewLayout: IViewLayout;
303
	private readonly _editorScrollbar: EditorScrollbar;
304

A
Alex Dima 已提交
305 306
	private readonly _domNode: FastDomNode<HTMLElement>;
	private readonly _canvas: FastDomNode<HTMLCanvasElement>;
307
	private readonly _slider: FastDomNode<HTMLElement>;
308 309
	private readonly _tokensColorTracker: MinimapTokensColorTracker;
	private readonly _tokensColorTrackerListener: IDisposable;
A
Alex Dima 已提交
310

A
Alex Dima 已提交
311 312
	private readonly _minimapCharRenderer: MinimapCharRenderer;

313
	private _options: MinimapOptions;
A
Alex Dima 已提交
314
	private _lastRenderData: RenderData;
315
	private _backgroundFillData: Uint8ClampedArray;
A
Alex Dima 已提交
316

317
	constructor(context: ViewContext, viewLayout: IViewLayout, editorScrollbar: EditorScrollbar) {
A
Alex Dima 已提交
318
		super(context);
319
		this._viewLayout = viewLayout;
320
		this._editorScrollbar = editorScrollbar;
A
Alex Dima 已提交
321

322
		this._options = new MinimapOptions(this._context.configuration);
A
Alex Dima 已提交
323
		this._lastRenderData = null;
324 325 326 327 328 329 330 331 332 333 334
		this._backgroundFillData = null;

		this._domNode = createFastDomNode(document.createElement('div'));
		this._domNode.setPosition('absolute');
		this._domNode.setRight(0);

		this._canvas = createFastDomNode(document.createElement('canvas'));
		this._canvas.setPosition('absolute');
		this._canvas.setLeft(0);
		this._domNode.domNode.appendChild(this._canvas.domNode);

335 336 337 338 339
		this._slider = createFastDomNode(document.createElement('div'));
		this._slider.setPosition('absolute');
		this._slider.setClassName('minimap-slider');
		this._domNode.domNode.appendChild(this._slider.domNode);

340 341 342
		this._tokensColorTracker = MinimapTokensColorTracker.getInstance();
		this._tokensColorTrackerListener = this._tokensColorTracker.onDidChange(() => this._backgroundFillData = null);

A
Alex Dima 已提交
343 344
		this._minimapCharRenderer = getOrCreateMinimapCharRenderer();

345
		this._applyLayout();
A
Alex Dima 已提交
346 347 348
	}

	public dispose(): void {
A
Alex Dima 已提交
349
		this._tokensColorTrackerListener.dispose();
A
Alex Dima 已提交
350
		super.dispose();
351 352 353 354 355 356 357 358 359 360 361
	}

	public getDomNode(): HTMLElement {
		return this._domNode.domNode;
	}

	private _applyLayout(): void {
		this._domNode.setWidth(this._options.minimapWidth);
		this._domNode.setHeight(this._options.minimapHeight);
		this._canvas.setWidth(this._options.canvasOuterWidth);
		this._canvas.setHeight(this._options.canvasOuterHeight);
A
Alex Dima 已提交
362 363
		this._canvas.domNode.width = this._options.canvasInnerWidth;
		this._canvas.domNode.height = this._options.canvasInnerHeight;
364
		this._slider.setWidth(this._options.minimapWidth);
365 366 367 368 369 370 371 372
		this._backgroundFillData = null;
	}

	private _getBackgroundFillData(): Uint8ClampedArray {
		if (this._backgroundFillData === null) {
			const WIDTH = this._options.canvasInnerWidth;
			const HEIGHT = this._options.canvasInnerHeight;

A
Alex Dima 已提交
373
			const background = this._tokensColorTracker.getColor(ColorId.DefaultBackground);
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
			const backgroundR = background.r;
			const backgroundG = background.g;
			const backgroundB = background.b;

			let result = new Uint8ClampedArray(WIDTH * HEIGHT * 4);
			let offset = 0;
			for (let i = 0; i < HEIGHT; i++) {
				for (let j = 0; j < WIDTH; j++) {
					result[offset] = backgroundR;
					result[offset + 1] = backgroundG;
					result[offset + 2] = backgroundB;
					result[offset + 3] = 255;
					offset += 4;
				}
			}

			this._backgroundFillData = result;
		}
		return this._backgroundFillData;
A
Alex Dima 已提交
393 394 395 396
	}

	// ---- begin view event handlers

397 398 399 400 401 402
	private _onOptionsMaybeChanged(): boolean {
		let opts = new MinimapOptions(this._context.configuration);
		if (this._options.equals(opts)) {
			return false;
		}
		this._options = opts;
A
Alex Dima 已提交
403
		this._lastRenderData = null;
404
		this._applyLayout();
A
Alex Dima 已提交
405 406
		return true;
	}
A
Alex Dima 已提交
407 408

	public onLineMappingChanged(): boolean {
A
Alex Dima 已提交
409
		this._lastRenderData = null;
A
Alex Dima 已提交
410 411 412
		return true;
	}
	public onModelFlushed(): boolean {
A
Alex Dima 已提交
413
		this._lastRenderData = null;
A
Alex Dima 已提交
414 415 416
		return true;
	}
	public onModelLinesDeleted(e: editorCommon.IViewLinesDeletedEvent): boolean {
A
Alex Dima 已提交
417 418 419
		if (this._lastRenderData) {
			this._lastRenderData.onModelLinesDeleted(e);
		}
A
Alex Dima 已提交
420 421 422
		return true;
	}
	public onModelLineChanged(e: editorCommon.IViewLineChangedEvent): boolean {
A
Alex Dima 已提交
423 424 425 426
		if (this._lastRenderData) {
			return this._lastRenderData.onModelLineChanged(e);
		}
		return false;
A
Alex Dima 已提交
427 428
	}
	public onModelLinesInserted(e: editorCommon.IViewLinesInsertedEvent): boolean {
A
Alex Dima 已提交
429 430 431
		if (this._lastRenderData) {
			this._lastRenderData.onModelLinesInserted(e);
		}
A
Alex Dima 已提交
432 433 434
		return true;
	}
	public onModelTokensChanged(e: editorCommon.IViewTokensChangedEvent): boolean {
A
Alex Dima 已提交
435 436 437 438
		if (this._lastRenderData) {
			return this._lastRenderData.onModelTokensChanged(e);
		}
		return false;
A
Alex Dima 已提交
439
	}
440 441 442 443 444 445
	public onConfigurationChanged(e: editorCommon.IConfigurationChangedEvent): boolean {
		return this._onOptionsMaybeChanged();
	}
	public onLayoutChanged(layoutInfo: editorCommon.EditorLayoutInfo): boolean {
		return this._onOptionsMaybeChanged();
	}
A
Alex Dima 已提交
446
	public onScrollChanged(e: editorCommon.IScrollEvent): boolean {
A
Alex Dima 已提交
447
		return e.scrollTopChanged || e.scrollHeightChanged;
448
	}
A
Alex Dima 已提交
449
	public onZonesChanged(): boolean {
A
Alex Dima 已提交
450
		this._lastRenderData = null;
A
Alex Dima 已提交
451 452
		return true;
	}
A
Alex Dima 已提交
453

A
Alex Dima 已提交
454
	// --- end event handlers
A
Alex Dima 已提交
455 456 457 458 459 460 461 462

	public prepareRender(ctx: IRenderingContext): void {
		// Nothing to read
		if (!this.shouldRender()) {
			throw new Error('I did not ask to render!');
		}
	}

463 464 465 466 467 468 469 470
	public render(renderingCtx: IRestrictedRenderingContext): void {
		const renderMinimap = this._options.renderMinimap;
		if (renderMinimap === RenderMinimap.None) {
			return;
		}

		const WIDTH = this._options.canvasInnerWidth;
		const HEIGHT = this._options.canvasInnerHeight;
A
Alex Dima 已提交
471
		const ctx = this._canvas.domNode.getContext('2d');
472 473
		const minimapLineHeight = (renderMinimap === RenderMinimap.Large ? Constants.x2_CHAR_HEIGHT : Constants.x1_CHAR_HEIGHT);
		const charWidth = (renderMinimap === RenderMinimap.Large ? Constants.x2_CHAR_WIDTH : Constants.x1_CHAR_WIDTH);
474

A
Alex Dima 已提交
475 476
		const layout = new MinimapLayout(
			this._lastRenderData,
477 478 479
			this._options,
			renderingCtx.visibleRange.startLineNumber,
			renderingCtx.visibleRange.endLineNumber,
480
			renderingCtx.viewportHeight,
481 482
			this._context.model.getLineCount(),
			this._editorScrollbar.getVerticalSliderVerticalCenter()
483
		);
A
Alex Dima 已提交
484

A
Alex Dima 已提交
485 486
		this._slider.setTop(layout.sliderTop);
		this._slider.setHeight(layout.sliderHeight);
A
Alex Dima 已提交
487

A
Alex Dima 已提交
488 489
		const startLineNumber = layout.startLineNumber;
		const endLineNumber = layout.endLineNumber;
A
Alex Dima 已提交
490

491 492 493 494
		// Prepare image data (fill with background color)
		let imageData = ctx.createImageData(WIDTH, HEIGHT);
		imageData.data.set(this._getBackgroundFillData());

A
Alex Dima 已提交
495
		let background = this._tokensColorTracker.getColor(ColorId.DefaultBackground);
496

A
Alex Dima 已提交
497
		let needed: boolean[] = [];
498
		for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
A
Alex Dima 已提交
499
			needed[lineNumber - startLineNumber] = true;
A
Alex Dima 已提交
500
		}
A
Alex Dima 已提交
501 502
		const data2 = this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed);
		const tabSize = data2.tabSize;
A
Alex Dima 已提交
503

A
Alex Dima 已提交
504
		let start2 = performance.now();
505
		let dy = 0;
A
Alex Dima 已提交
506 507
		let renderedLines: MinimapLine[] = [];
		// TODO@minimap: paint using old image data
508
		for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
A
Alex Dima 已提交
509 510 511
			let lineIndex = lineNumber - startLineNumber;
			Minimap._renderLine(imageData, background, renderMinimap, charWidth, this._tokensColorTracker, this._minimapCharRenderer, dy, tabSize, data2.data[lineIndex]);
			renderedLines[lineIndex] = new MinimapLine(dy);
512
			dy += minimapLineHeight;
A
Alex Dima 已提交
513
		}
A
Alex Dima 已提交
514 515
		let end2 = performance.now();
		console.log(`PAINTING MINIMAP TOOK ${end2 - start2} ms.`);
A
Alex Dima 已提交
516

A
Alex Dima 已提交
517
		let renderedLayout = new RenderedLayout(
A
Alex Dima 已提交
518 519 520
			renderingCtx.visibleRange.startLineNumber,
			renderingCtx.visibleRange.endLineNumber,
			startLineNumber,
A
Alex Dima 已提交
521 522 523 524 525 526
			endLineNumber,
		);
		this._lastRenderData = new RenderData(
			renderedLayout,
			imageData,
			renderedLines
A
Alex Dima 已提交
527 528
		);

529
		ctx.putImageData(imageData, 0, 0);
A
Alex Dima 已提交
530 531
	}

A
Alex Dima 已提交
532 533 534 535 536 537 538 539
	private static _renderLine(
		target: ImageData,
		backgroundColor: ParsedColor,
		renderMinimap: RenderMinimap,
		charWidth: number,
		colorTracker: MinimapTokensColorTracker,
		minimapCharRenderer: MinimapCharRenderer,
		dy: number,
A
Alex Dima 已提交
540
		tabSize: number,
A
Alex Dima 已提交
541
		lineData: ViewLineData
A
Alex Dima 已提交
542
	): void {
A
Alex Dima 已提交
543 544
		const content = lineData.content;
		const tokens = lineData.tokens;
545
		const maxDx = target.width - charWidth;
A
Alex Dima 已提交
546 547 548

		let dx = 0;
		let charIndex = 0;
549 550
		let tabsCharDelta = 0;

A
Alex Dima 已提交
551 552 553 554
		for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) {
			const token = tokens[tokenIndex];
			const tokenEndIndex = token.endIndex;
			const tokenColorId = token.getForeground();
A
Alex Dima 已提交
555
			const tokenColor = colorTracker.getColor(tokenColorId);
A
Alex Dima 已提交
556 557

			for (; charIndex < tokenEndIndex; charIndex++) {
A
Alex Dima 已提交
558
				if (dx > maxDx) {
A
Alex Dima 已提交
559 560 561 562 563
					// hit edge of minimap
					return;
				}
				const charCode = content.charCodeAt(charIndex);

564 565 566 567
				if (charCode === CharCode.Tab) {
					let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
					tabsCharDelta += insertSpacesCount - 1;
					// No need to render anything since tab is invisible
568
					dx += insertSpacesCount * charWidth;
569 570
				} else if (charCode === CharCode.Space) {
					// No need to render anything since space is invisible
571
					dx += charWidth;
572
				} else {
573
					if (renderMinimap === RenderMinimap.Large) {
A
Alex Dima 已提交
574
						minimapCharRenderer.x2RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor);
575
					} else {
A
Alex Dima 已提交
576
						minimapCharRenderer.x1RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor);
577 578
					}
					dx += charWidth;
579
				}
A
Alex Dima 已提交
580 581 582 583
			}
		}
	}
}