| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import './minimap.css';
- import * as dom from '../../../../base/browser/dom.js';
- import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
- import { GlobalPointerMoveMonitor } from '../../../../base/browser/globalPointerMoveMonitor.js';
- import { Disposable } from '../../../../base/common/lifecycle.js';
- import * as platform from '../../../../base/common/platform.js';
- import * as strings from '../../../../base/common/strings.js';
- import { RenderedLinesCollection } from '../../view/viewLayer.js';
- import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
- import { MINIMAP_GUTTER_WIDTH, EditorLayoutInfoComputer } from '../../../common/config/editorOptions.js';
- import { Range } from '../../../common/core/range.js';
- import { RGBA8 } from '../../../common/core/rgba.js';
- import { MinimapTokensColorTracker } from '../../../common/viewModel/minimapTokensColorTracker.js';
- import { ViewModelDecoration } from '../../../common/viewModel.js';
- import { minimapSelection, scrollbarShadow, minimapBackground, minimapSliderBackground, minimapSliderHoverBackground, minimapSliderActiveBackground, minimapForegroundOpacity } from '../../../../platform/theme/common/colorRegistry.js';
- import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
- import { Selection } from '../../../common/core/selection.js';
- import { EventType, Gesture } from '../../../../base/browser/touch.js';
- import { MinimapCharRendererFactory } from './minimapCharRendererFactory.js';
- import { MinimapPosition } from '../../../common/model.js';
- import { once } from '../../../../base/common/functional.js';
- /**
- * The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
- */
- const POINTER_DRAG_RESET_DISTANCE = 140;
- const GUTTER_DECORATION_WIDTH = 2;
- class MinimapOptions {
- constructor(configuration, theme, tokensColorTracker) {
- const options = configuration.options;
- const pixelRatio = options.get(131 /* EditorOption.pixelRatio */);
- const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
- const minimapLayout = layoutInfo.minimap;
- const fontInfo = options.get(46 /* EditorOption.fontInfo */);
- const minimapOpts = options.get(67 /* EditorOption.minimap */);
- this.renderMinimap = minimapLayout.renderMinimap;
- this.size = minimapOpts.size;
- this.minimapHeightIsEditorHeight = minimapLayout.minimapHeightIsEditorHeight;
- this.scrollBeyondLastLine = options.get(96 /* EditorOption.scrollBeyondLastLine */);
- this.showSlider = minimapOpts.showSlider;
- this.autohide = minimapOpts.autohide;
- this.pixelRatio = pixelRatio;
- this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
- this.lineHeight = options.get(61 /* EditorOption.lineHeight */);
- this.minimapLeft = minimapLayout.minimapLeft;
- this.minimapWidth = minimapLayout.minimapWidth;
- this.minimapHeight = layoutInfo.height;
- this.canvasInnerWidth = minimapLayout.minimapCanvasInnerWidth;
- this.canvasInnerHeight = minimapLayout.minimapCanvasInnerHeight;
- this.canvasOuterWidth = minimapLayout.minimapCanvasOuterWidth;
- this.canvasOuterHeight = minimapLayout.minimapCanvasOuterHeight;
- this.isSampling = minimapLayout.minimapIsSampling;
- this.editorHeight = layoutInfo.height;
- this.fontScale = minimapLayout.minimapScale;
- this.minimapLineHeight = minimapLayout.minimapLineHeight;
- this.minimapCharWidth = 1 /* Constants.BASE_CHAR_WIDTH */ * this.fontScale;
- this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily));
- this.defaultBackgroundColor = tokensColorTracker.getColor(2 /* ColorId.DefaultBackground */);
- this.backgroundColor = MinimapOptions._getMinimapBackground(theme, this.defaultBackgroundColor);
- this.foregroundAlpha = MinimapOptions._getMinimapForegroundOpacity(theme);
- }
- static _getMinimapBackground(theme, defaultBackgroundColor) {
- const themeColor = theme.getColor(minimapBackground);
- if (themeColor) {
- return new RGBA8(themeColor.rgba.r, themeColor.rgba.g, themeColor.rgba.b, Math.round(255 * themeColor.rgba.a));
- }
- return defaultBackgroundColor;
- }
- static _getMinimapForegroundOpacity(theme) {
- const themeColor = theme.getColor(minimapForegroundOpacity);
- if (themeColor) {
- return RGBA8._clamp(Math.round(255 * themeColor.rgba.a));
- }
- return 255;
- }
- equals(other) {
- return (this.renderMinimap === other.renderMinimap
- && this.size === other.size
- && this.minimapHeightIsEditorHeight === other.minimapHeightIsEditorHeight
- && this.scrollBeyondLastLine === other.scrollBeyondLastLine
- && this.showSlider === other.showSlider
- && this.autohide === other.autohide
- && this.pixelRatio === other.pixelRatio
- && this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth
- && this.lineHeight === other.lineHeight
- && this.minimapLeft === other.minimapLeft
- && this.minimapWidth === other.minimapWidth
- && this.minimapHeight === other.minimapHeight
- && this.canvasInnerWidth === other.canvasInnerWidth
- && this.canvasInnerHeight === other.canvasInnerHeight
- && this.canvasOuterWidth === other.canvasOuterWidth
- && this.canvasOuterHeight === other.canvasOuterHeight
- && this.isSampling === other.isSampling
- && this.editorHeight === other.editorHeight
- && this.fontScale === other.fontScale
- && this.minimapLineHeight === other.minimapLineHeight
- && this.minimapCharWidth === other.minimapCharWidth
- && this.defaultBackgroundColor && this.defaultBackgroundColor.equals(other.defaultBackgroundColor)
- && this.backgroundColor && this.backgroundColor.equals(other.backgroundColor)
- && this.foregroundAlpha === other.foregroundAlpha);
- }
- }
- class MinimapLayout {
- constructor(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber) {
- this.scrollTop = scrollTop;
- this.scrollHeight = scrollHeight;
- this.sliderNeeded = sliderNeeded;
- this._computedSliderRatio = computedSliderRatio;
- this.sliderTop = sliderTop;
- this.sliderHeight = sliderHeight;
- this.startLineNumber = startLineNumber;
- this.endLineNumber = endLineNumber;
- }
- /**
- * Compute a desired `scrollPosition` such that the slider moves by `delta`.
- */
- getDesiredScrollTopFromDelta(delta) {
- return Math.round(this.scrollTop + delta / this._computedSliderRatio);
- }
- getDesiredScrollTopFromTouchLocation(pageY) {
- return Math.round((pageY - this.sliderHeight / 2) / this._computedSliderRatio);
- }
- static create(options, viewportStartLineNumber, viewportEndLineNumber, viewportStartLineNumberVerticalOffset, viewportHeight, viewportContainsWhitespaceGaps, lineCount, realLineCount, scrollTop, scrollHeight, previousLayout) {
- const pixelRatio = options.pixelRatio;
- const minimapLineHeight = options.minimapLineHeight;
- const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight);
- const lineHeight = options.lineHeight;
- if (options.minimapHeightIsEditorHeight) {
- const logicalScrollHeight = (realLineCount * options.lineHeight
- + (options.scrollBeyondLastLine ? viewportHeight - options.lineHeight : 0));
- const sliderHeight = Math.max(1, Math.floor(viewportHeight * viewportHeight / logicalScrollHeight));
- const maxMinimapSliderTop = Math.max(0, options.minimapHeight - sliderHeight);
- // The slider can move from 0 to `maxMinimapSliderTop`
- // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`.
- const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight);
- const sliderTop = (scrollTop * computedSliderRatio);
- const sliderNeeded = (maxMinimapSliderTop > 0);
- const maxLinesFitting = Math.floor(options.canvasInnerHeight / options.minimapLineHeight);
- return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, Math.min(lineCount, maxLinesFitting));
- }
- // The visible line count in a viewport can change due to a number of reasons:
- // a) with the same viewport width, different scroll positions can result in partial lines being visible:
- // e.g. for a line height of 20, and a viewport height of 600
- // * scrollTop = 0 => visible lines are [1, 30]
- // * scrollTop = 10 => visible lines are [1, 31] (with lines 1 and 31 partially visible)
- // * scrollTop = 20 => visible lines are [2, 31]
- // b) whitespace gaps might make their way in the viewport (which results in a decrease in the visible line count)
- // c) we could be in the scroll beyond last line case (which also results in a decrease in the visible line count, down to possibly only one line being visible)
- // We must first establish a desirable slider height.
- let sliderHeight;
- if (viewportContainsWhitespaceGaps && viewportEndLineNumber !== lineCount) {
- // case b) from above: there are whitespace gaps in the viewport.
- // In this case, the height of the slider directly reflects the visible line count.
- const viewportLineCount = viewportEndLineNumber - viewportStartLineNumber + 1;
- sliderHeight = Math.floor(viewportLineCount * minimapLineHeight / pixelRatio);
- }
- else {
- // The slider has a stable height
- const expectedViewportLineCount = viewportHeight / lineHeight;
- sliderHeight = Math.floor(expectedViewportLineCount * minimapLineHeight / pixelRatio);
- }
- let maxMinimapSliderTop;
- if (options.scrollBeyondLastLine) {
- // The minimap slider, when dragged all the way down, will contain the last line at its top
- maxMinimapSliderTop = (lineCount - 1) * minimapLineHeight / pixelRatio;
- }
- else {
- // The minimap slider, when dragged all the way down, will contain the last line at its bottom
- maxMinimapSliderTop = Math.max(0, lineCount * minimapLineHeight / pixelRatio - sliderHeight);
- }
- maxMinimapSliderTop = Math.min(options.minimapHeight - sliderHeight, maxMinimapSliderTop);
- // The slider can move from 0 to `maxMinimapSliderTop`
- // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`.
- const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight);
- const sliderTop = (scrollTop * computedSliderRatio);
- let extraLinesAtTheBottom = 0;
- if (options.scrollBeyondLastLine) {
- const expectedViewportLineCount = viewportHeight / lineHeight;
- extraLinesAtTheBottom = expectedViewportLineCount - 1;
- }
- if (minimapLinesFitting >= lineCount + extraLinesAtTheBottom) {
- // All lines fit in the minimap
- const startLineNumber = 1;
- const endLineNumber = lineCount;
- const sliderNeeded = (maxMinimapSliderTop > 0);
- return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber);
- }
- else {
- let startLineNumber = Math.max(1, Math.floor(viewportStartLineNumber - sliderTop * pixelRatio / minimapLineHeight));
- // Avoid flickering caused by a partial viewport start line
- // by being consistent w.r.t. the previous layout decision
- if (previousLayout && previousLayout.scrollHeight === scrollHeight) {
- if (previousLayout.scrollTop > scrollTop) {
- // Scrolling up => never increase `startLineNumber`
- startLineNumber = Math.min(startLineNumber, previousLayout.startLineNumber);
- }
- if (previousLayout.scrollTop < scrollTop) {
- // Scrolling down => never decrease `startLineNumber`
- startLineNumber = Math.max(startLineNumber, previousLayout.startLineNumber);
- }
- }
- const endLineNumber = Math.min(lineCount, startLineNumber + minimapLinesFitting - 1);
- const partialLine = (scrollTop - viewportStartLineNumberVerticalOffset) / lineHeight;
- const sliderTopAligned = (viewportStartLineNumber - startLineNumber + partialLine) * minimapLineHeight / pixelRatio;
- return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTopAligned, sliderHeight, startLineNumber, endLineNumber);
- }
- }
- }
- class MinimapLine {
- constructor(dy) {
- this.dy = dy;
- }
- onContentChanged() {
- this.dy = -1;
- }
- onTokensChanged() {
- this.dy = -1;
- }
- }
- MinimapLine.INVALID = new MinimapLine(-1);
- class RenderData {
- constructor(renderedLayout, imageData, lines) {
- this.renderedLayout = renderedLayout;
- this._imageData = imageData;
- this._renderedLines = new RenderedLinesCollection(() => MinimapLine.INVALID);
- this._renderedLines._set(renderedLayout.startLineNumber, lines);
- }
- /**
- * Check if the current RenderData matches accurately the new desired layout and no painting is needed.
- */
- linesEquals(layout) {
- if (!this.scrollEquals(layout)) {
- return false;
- }
- const tmp = this._renderedLines._get();
- const lines = tmp.lines;
- for (let i = 0, len = lines.length; i < len; i++) {
- if (lines[i].dy === -1) {
- // This line is invalid
- return false;
- }
- }
- return true;
- }
- /**
- * Check if the current RenderData matches the new layout's scroll position
- */
- scrollEquals(layout) {
- return this.renderedLayout.startLineNumber === layout.startLineNumber
- && this.renderedLayout.endLineNumber === layout.endLineNumber;
- }
- _get() {
- const tmp = this._renderedLines._get();
- return {
- imageData: this._imageData,
- rendLineNumberStart: tmp.rendLineNumberStart,
- lines: tmp.lines
- };
- }
- onLinesChanged(changeFromLineNumber, changeCount) {
- return this._renderedLines.onLinesChanged(changeFromLineNumber, changeCount);
- }
- onLinesDeleted(deleteFromLineNumber, deleteToLineNumber) {
- this._renderedLines.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber);
- }
- onLinesInserted(insertFromLineNumber, insertToLineNumber) {
- this._renderedLines.onLinesInserted(insertFromLineNumber, insertToLineNumber);
- }
- onTokensChanged(ranges) {
- return this._renderedLines.onTokensChanged(ranges);
- }
- }
- /**
- * Some sort of double buffering.
- *
- * Keeps two buffers around that will be rotated for painting.
- * Always gives a buffer that is filled with the background color.
- */
- class MinimapBuffers {
- constructor(ctx, WIDTH, HEIGHT, background) {
- this._backgroundFillData = MinimapBuffers._createBackgroundFillData(WIDTH, HEIGHT, background);
- this._buffers = [
- ctx.createImageData(WIDTH, HEIGHT),
- ctx.createImageData(WIDTH, HEIGHT)
- ];
- this._lastUsedBuffer = 0;
- }
- getBuffer() {
- // rotate buffers
- this._lastUsedBuffer = 1 - this._lastUsedBuffer;
- const result = this._buffers[this._lastUsedBuffer];
- // fill with background color
- result.data.set(this._backgroundFillData);
- return result;
- }
- static _createBackgroundFillData(WIDTH, HEIGHT, background) {
- const backgroundR = background.r;
- const backgroundG = background.g;
- const backgroundB = background.b;
- const backgroundA = background.a;
- const 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] = backgroundA;
- offset += 4;
- }
- }
- return result;
- }
- }
- class MinimapSamplingState {
- constructor(samplingRatio, minimapLines) {
- this.samplingRatio = samplingRatio;
- this.minimapLines = minimapLines;
- }
- static compute(options, viewLineCount, oldSamplingState) {
- if (options.renderMinimap === 0 /* RenderMinimap.None */ || !options.isSampling) {
- return [null, []];
- }
- // ratio is intentionally not part of the layout to avoid the layout changing all the time
- // so we need to recompute it again...
- const pixelRatio = options.pixelRatio;
- const lineHeight = options.lineHeight;
- const scrollBeyondLastLine = options.scrollBeyondLastLine;
- const { minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
- viewLineCount: viewLineCount,
- scrollBeyondLastLine: scrollBeyondLastLine,
- height: options.editorHeight,
- lineHeight: lineHeight,
- pixelRatio: pixelRatio
- });
- const ratio = viewLineCount / minimapLineCount;
- const halfRatio = ratio / 2;
- if (!oldSamplingState || oldSamplingState.minimapLines.length === 0) {
- const result = [];
- result[0] = 1;
- if (minimapLineCount > 1) {
- for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) {
- result[i] = Math.round(i * ratio + halfRatio);
- }
- result[minimapLineCount - 1] = viewLineCount;
- }
- return [new MinimapSamplingState(ratio, result), []];
- }
- const oldMinimapLines = oldSamplingState.minimapLines;
- const oldLength = oldMinimapLines.length;
- const result = [];
- let oldIndex = 0;
- let oldDeltaLineCount = 0;
- let minViewLineNumber = 1;
- const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data
- let events = [];
- let lastEvent = null;
- for (let i = 0; i < minimapLineCount; i++) {
- const fromViewLineNumber = Math.max(minViewLineNumber, Math.round(i * ratio));
- const toViewLineNumber = Math.max(fromViewLineNumber, Math.round((i + 1) * ratio));
- while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromViewLineNumber) {
- if (events.length < MAX_EVENT_COUNT) {
- const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
- if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) {
- lastEvent.deleteToLineNumber++;
- }
- else {
- lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber };
- events.push(lastEvent);
- }
- oldDeltaLineCount--;
- }
- oldIndex++;
- }
- let selectedViewLineNumber;
- if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toViewLineNumber) {
- // reuse the old sampled line
- selectedViewLineNumber = oldMinimapLines[oldIndex];
- oldIndex++;
- }
- else {
- if (i === 0) {
- selectedViewLineNumber = 1;
- }
- else if (i + 1 === minimapLineCount) {
- selectedViewLineNumber = viewLineCount;
- }
- else {
- selectedViewLineNumber = Math.round(i * ratio + halfRatio);
- }
- if (events.length < MAX_EVENT_COUNT) {
- const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
- if (lastEvent && lastEvent.type === 'inserted' && lastEvent._i === i - 1) {
- lastEvent.insertToLineNumber++;
- }
- else {
- lastEvent = { type: 'inserted', _i: i, insertFromLineNumber: oldMinimapLineNumber, insertToLineNumber: oldMinimapLineNumber };
- events.push(lastEvent);
- }
- oldDeltaLineCount++;
- }
- }
- result[i] = selectedViewLineNumber;
- minViewLineNumber = selectedViewLineNumber;
- }
- if (events.length < MAX_EVENT_COUNT) {
- while (oldIndex < oldLength) {
- const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
- if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) {
- lastEvent.deleteToLineNumber++;
- }
- else {
- lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber };
- events.push(lastEvent);
- }
- oldDeltaLineCount--;
- oldIndex++;
- }
- }
- else {
- // too many events, just give up
- events = [{ type: 'flush' }];
- }
- return [new MinimapSamplingState(ratio, result), events];
- }
- modelLineToMinimapLine(lineNumber) {
- return Math.min(this.minimapLines.length, Math.max(1, Math.round(lineNumber / this.samplingRatio)));
- }
- /**
- * Will return null if the model line ranges are not intersecting with a sampled model line.
- */
- modelLineRangeToMinimapLineRange(fromLineNumber, toLineNumber) {
- let fromLineIndex = this.modelLineToMinimapLine(fromLineNumber) - 1;
- while (fromLineIndex > 0 && this.minimapLines[fromLineIndex - 1] >= fromLineNumber) {
- fromLineIndex--;
- }
- let toLineIndex = this.modelLineToMinimapLine(toLineNumber) - 1;
- while (toLineIndex + 1 < this.minimapLines.length && this.minimapLines[toLineIndex + 1] <= toLineNumber) {
- toLineIndex++;
- }
- if (fromLineIndex === toLineIndex) {
- const sampledLineNumber = this.minimapLines[fromLineIndex];
- if (sampledLineNumber < fromLineNumber || sampledLineNumber > toLineNumber) {
- // This line is not part of the sampled lines ==> nothing to do
- return null;
- }
- }
- return [fromLineIndex + 1, toLineIndex + 1];
- }
- /**
- * Will always return a range, even if it is not intersecting with a sampled model line.
- */
- decorationLineRangeToMinimapLineRange(startLineNumber, endLineNumber) {
- let minimapLineStart = this.modelLineToMinimapLine(startLineNumber);
- let minimapLineEnd = this.modelLineToMinimapLine(endLineNumber);
- if (startLineNumber !== endLineNumber && minimapLineEnd === minimapLineStart) {
- if (minimapLineEnd === this.minimapLines.length) {
- if (minimapLineStart > 1) {
- minimapLineStart--;
- }
- }
- else {
- minimapLineEnd++;
- }
- }
- return [minimapLineStart, minimapLineEnd];
- }
- onLinesDeleted(e) {
- // have the mapping be sticky
- const deletedLineCount = e.toLineNumber - e.fromLineNumber + 1;
- let changeStartIndex = this.minimapLines.length;
- let changeEndIndex = 0;
- for (let i = this.minimapLines.length - 1; i >= 0; i--) {
- if (this.minimapLines[i] < e.fromLineNumber) {
- break;
- }
- if (this.minimapLines[i] <= e.toLineNumber) {
- // this line got deleted => move to previous available
- this.minimapLines[i] = Math.max(1, e.fromLineNumber - 1);
- changeStartIndex = Math.min(changeStartIndex, i);
- changeEndIndex = Math.max(changeEndIndex, i);
- }
- else {
- this.minimapLines[i] -= deletedLineCount;
- }
- }
- return [changeStartIndex, changeEndIndex];
- }
- onLinesInserted(e) {
- // have the mapping be sticky
- const insertedLineCount = e.toLineNumber - e.fromLineNumber + 1;
- for (let i = this.minimapLines.length - 1; i >= 0; i--) {
- if (this.minimapLines[i] < e.fromLineNumber) {
- break;
- }
- this.minimapLines[i] += insertedLineCount;
- }
- }
- }
- export class Minimap extends ViewPart {
- constructor(context) {
- super(context);
- this.tokensColorTracker = MinimapTokensColorTracker.getInstance();
- this._selections = [];
- this._minimapSelections = null;
- this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker);
- const [samplingState,] = MinimapSamplingState.compute(this.options, this._context.viewModel.getLineCount(), null);
- this._samplingState = samplingState;
- this._shouldCheckSampling = false;
- this._actual = new InnerMinimap(context.theme, this);
- }
- dispose() {
- this._actual.dispose();
- super.dispose();
- }
- getDomNode() {
- return this._actual.getDomNode();
- }
- _onOptionsMaybeChanged() {
- const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker);
- if (this.options.equals(opts)) {
- return false;
- }
- this.options = opts;
- this._recreateLineSampling();
- this._actual.onDidChangeOptions();
- return true;
- }
- // ---- begin view event handlers
- onConfigurationChanged(e) {
- return this._onOptionsMaybeChanged();
- }
- onCursorStateChanged(e) {
- this._selections = e.selections;
- this._minimapSelections = null;
- return this._actual.onSelectionChanged();
- }
- onDecorationsChanged(e) {
- if (e.affectsMinimap) {
- return this._actual.onDecorationsChanged();
- }
- return false;
- }
- onFlushed(e) {
- if (this._samplingState) {
- this._shouldCheckSampling = true;
- }
- return this._actual.onFlushed();
- }
- onLinesChanged(e) {
- if (this._samplingState) {
- const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(e.fromLineNumber, e.fromLineNumber + e.count - 1);
- if (minimapLineRange) {
- return this._actual.onLinesChanged(minimapLineRange[0], minimapLineRange[1] - minimapLineRange[0] + 1);
- }
- else {
- return false;
- }
- }
- else {
- return this._actual.onLinesChanged(e.fromLineNumber, e.count);
- }
- }
- onLinesDeleted(e) {
- if (this._samplingState) {
- const [changeStartIndex, changeEndIndex] = this._samplingState.onLinesDeleted(e);
- if (changeStartIndex <= changeEndIndex) {
- this._actual.onLinesChanged(changeStartIndex + 1, changeEndIndex - changeStartIndex + 1);
- }
- this._shouldCheckSampling = true;
- return true;
- }
- else {
- return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber);
- }
- }
- onLinesInserted(e) {
- if (this._samplingState) {
- this._samplingState.onLinesInserted(e);
- this._shouldCheckSampling = true;
- return true;
- }
- else {
- return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber);
- }
- }
- onScrollChanged(e) {
- return this._actual.onScrollChanged();
- }
- onThemeChanged(e) {
- this._actual.onThemeChanged();
- this._onOptionsMaybeChanged();
- return true;
- }
- onTokensChanged(e) {
- if (this._samplingState) {
- const ranges = [];
- for (const range of e.ranges) {
- const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(range.fromLineNumber, range.toLineNumber);
- if (minimapLineRange) {
- ranges.push({ fromLineNumber: minimapLineRange[0], toLineNumber: minimapLineRange[1] });
- }
- }
- if (ranges.length) {
- return this._actual.onTokensChanged(ranges);
- }
- else {
- return false;
- }
- }
- else {
- return this._actual.onTokensChanged(e.ranges);
- }
- }
- onTokensColorsChanged(e) {
- this._onOptionsMaybeChanged();
- return this._actual.onTokensColorsChanged();
- }
- onZonesChanged(e) {
- return this._actual.onZonesChanged();
- }
- // --- end event handlers
- prepareRender(ctx) {
- if (this._shouldCheckSampling) {
- this._shouldCheckSampling = false;
- this._recreateLineSampling();
- }
- }
- render(ctx) {
- let viewportStartLineNumber = ctx.visibleRange.startLineNumber;
- let viewportEndLineNumber = ctx.visibleRange.endLineNumber;
- if (this._samplingState) {
- viewportStartLineNumber = this._samplingState.modelLineToMinimapLine(viewportStartLineNumber);
- viewportEndLineNumber = this._samplingState.modelLineToMinimapLine(viewportEndLineNumber);
- }
- const minimapCtx = {
- viewportContainsWhitespaceGaps: (ctx.viewportData.whitespaceViewportData.length > 0),
- scrollWidth: ctx.scrollWidth,
- scrollHeight: ctx.scrollHeight,
- viewportStartLineNumber: viewportStartLineNumber,
- viewportEndLineNumber: viewportEndLineNumber,
- viewportStartLineNumberVerticalOffset: ctx.getVerticalOffsetForLineNumber(viewportStartLineNumber),
- scrollTop: ctx.scrollTop,
- scrollLeft: ctx.scrollLeft,
- viewportWidth: ctx.viewportWidth,
- viewportHeight: ctx.viewportHeight,
- };
- this._actual.render(minimapCtx);
- }
- //#region IMinimapModel
- _recreateLineSampling() {
- this._minimapSelections = null;
- const wasSampling = Boolean(this._samplingState);
- const [samplingState, events] = MinimapSamplingState.compute(this.options, this._context.viewModel.getLineCount(), this._samplingState);
- this._samplingState = samplingState;
- if (wasSampling && this._samplingState) {
- // was sampling, is sampling
- for (const event of events) {
- switch (event.type) {
- case 'deleted':
- this._actual.onLinesDeleted(event.deleteFromLineNumber, event.deleteToLineNumber);
- break;
- case 'inserted':
- this._actual.onLinesInserted(event.insertFromLineNumber, event.insertToLineNumber);
- break;
- case 'flush':
- this._actual.onFlushed();
- break;
- }
- }
- }
- }
- getLineCount() {
- if (this._samplingState) {
- return this._samplingState.minimapLines.length;
- }
- return this._context.viewModel.getLineCount();
- }
- getRealLineCount() {
- return this._context.viewModel.getLineCount();
- }
- getLineContent(lineNumber) {
- if (this._samplingState) {
- return this._context.viewModel.getLineContent(this._samplingState.minimapLines[lineNumber - 1]);
- }
- return this._context.viewModel.getLineContent(lineNumber);
- }
- getLineMaxColumn(lineNumber) {
- if (this._samplingState) {
- return this._context.viewModel.getLineMaxColumn(this._samplingState.minimapLines[lineNumber - 1]);
- }
- return this._context.viewModel.getLineMaxColumn(lineNumber);
- }
- getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed) {
- if (this._samplingState) {
- const result = [];
- for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) {
- if (needed[lineIndex]) {
- result[lineIndex] = this._context.viewModel.getViewLineData(this._samplingState.minimapLines[startLineNumber + lineIndex - 1]);
- }
- else {
- result[lineIndex] = null;
- }
- }
- return result;
- }
- return this._context.viewModel.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed).data;
- }
- getSelections() {
- if (this._minimapSelections === null) {
- if (this._samplingState) {
- this._minimapSelections = [];
- for (const selection of this._selections) {
- const [minimapLineStart, minimapLineEnd] = this._samplingState.decorationLineRangeToMinimapLineRange(selection.startLineNumber, selection.endLineNumber);
- this._minimapSelections.push(new Selection(minimapLineStart, selection.startColumn, minimapLineEnd, selection.endColumn));
- }
- }
- else {
- this._minimapSelections = this._selections;
- }
- }
- return this._minimapSelections;
- }
- getMinimapDecorationsInViewport(startLineNumber, endLineNumber) {
- let visibleRange;
- if (this._samplingState) {
- const modelStartLineNumber = this._samplingState.minimapLines[startLineNumber - 1];
- const modelEndLineNumber = this._samplingState.minimapLines[endLineNumber - 1];
- visibleRange = new Range(modelStartLineNumber, 1, modelEndLineNumber, this._context.viewModel.getLineMaxColumn(modelEndLineNumber));
- }
- else {
- visibleRange = new Range(startLineNumber, 1, endLineNumber, this._context.viewModel.getLineMaxColumn(endLineNumber));
- }
- const decorations = this._context.viewModel.getDecorationsInViewport(visibleRange);
- if (this._samplingState) {
- const result = [];
- for (const decoration of decorations) {
- if (!decoration.options.minimap) {
- continue;
- }
- const range = decoration.range;
- const minimapStartLineNumber = this._samplingState.modelLineToMinimapLine(range.startLineNumber);
- const minimapEndLineNumber = this._samplingState.modelLineToMinimapLine(range.endLineNumber);
- result.push(new ViewModelDecoration(new Range(minimapStartLineNumber, range.startColumn, minimapEndLineNumber, range.endColumn), decoration.options));
- }
- return result;
- }
- return decorations;
- }
- getOptions() {
- return this._context.viewModel.model.getOptions();
- }
- revealLineNumber(lineNumber) {
- if (this._samplingState) {
- lineNumber = this._samplingState.minimapLines[lineNumber - 1];
- }
- this._context.viewModel.revealRange('mouse', false, new Range(lineNumber, 1, lineNumber, 1), 1 /* viewEvents.VerticalRevealType.Center */, 0 /* ScrollType.Smooth */);
- }
- setScrollTop(scrollTop) {
- this._context.viewModel.viewLayout.setScrollPosition({
- scrollTop: scrollTop
- }, 1 /* ScrollType.Immediate */);
- }
- }
- class InnerMinimap extends Disposable {
- constructor(theme, model) {
- super();
- this._renderDecorations = false;
- this._gestureInProgress = false;
- this._theme = theme;
- this._model = model;
- this._lastRenderData = null;
- this._buffers = null;
- this._selectionColor = this._theme.getColor(minimapSelection);
- this._domNode = createFastDomNode(document.createElement('div'));
- PartFingerprints.write(this._domNode, 8 /* PartFingerprint.Minimap */);
- this._domNode.setClassName(this._getMinimapDomNodeClassName());
- this._domNode.setPosition('absolute');
- this._domNode.setAttribute('role', 'presentation');
- this._domNode.setAttribute('aria-hidden', 'true');
- this._shadow = createFastDomNode(document.createElement('div'));
- this._shadow.setClassName('minimap-shadow-hidden');
- this._domNode.appendChild(this._shadow);
- this._canvas = createFastDomNode(document.createElement('canvas'));
- this._canvas.setPosition('absolute');
- this._canvas.setLeft(0);
- this._domNode.appendChild(this._canvas);
- this._decorationsCanvas = createFastDomNode(document.createElement('canvas'));
- this._decorationsCanvas.setPosition('absolute');
- this._decorationsCanvas.setClassName('minimap-decorations-layer');
- this._decorationsCanvas.setLeft(0);
- this._domNode.appendChild(this._decorationsCanvas);
- this._slider = createFastDomNode(document.createElement('div'));
- this._slider.setPosition('absolute');
- this._slider.setClassName('minimap-slider');
- this._slider.setLayerHinting(true);
- this._slider.setContain('strict');
- this._domNode.appendChild(this._slider);
- this._sliderHorizontal = createFastDomNode(document.createElement('div'));
- this._sliderHorizontal.setPosition('absolute');
- this._sliderHorizontal.setClassName('minimap-slider-horizontal');
- this._slider.appendChild(this._sliderHorizontal);
- this._applyLayout();
- this._pointerDownListener = dom.addStandardDisposableListener(this._domNode.domNode, dom.EventType.POINTER_DOWN, (e) => {
- e.preventDefault();
- const renderMinimap = this._model.options.renderMinimap;
- if (renderMinimap === 0 /* RenderMinimap.None */) {
- return;
- }
- if (!this._lastRenderData) {
- return;
- }
- if (this._model.options.size !== 'proportional') {
- if (e.button === 0 && this._lastRenderData) {
- // pretend the click occurred in the center of the slider
- const position = dom.getDomNodePagePosition(this._slider.domNode);
- const initialPosY = position.top + position.height / 2;
- this._startSliderDragging(e, initialPosY, this._lastRenderData.renderedLayout);
- }
- return;
- }
- const minimapLineHeight = this._model.options.minimapLineHeight;
- const internalOffsetY = (this._model.options.canvasInnerHeight / this._model.options.canvasOuterHeight) * e.offsetY;
- const lineIndex = Math.floor(internalOffsetY / minimapLineHeight);
- let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber;
- lineNumber = Math.min(lineNumber, this._model.getLineCount());
- this._model.revealLineNumber(lineNumber);
- });
- this._sliderPointerMoveMonitor = new GlobalPointerMoveMonitor();
- this._sliderPointerDownListener = dom.addStandardDisposableListener(this._slider.domNode, dom.EventType.POINTER_DOWN, (e) => {
- e.preventDefault();
- e.stopPropagation();
- if (e.button === 0 && this._lastRenderData) {
- this._startSliderDragging(e, e.pageY, this._lastRenderData.renderedLayout);
- }
- });
- this._gestureDisposable = Gesture.addTarget(this._domNode.domNode);
- this._sliderTouchStartListener = dom.addDisposableListener(this._domNode.domNode, EventType.Start, (e) => {
- e.preventDefault();
- e.stopPropagation();
- if (this._lastRenderData) {
- this._slider.toggleClassName('active', true);
- this._gestureInProgress = true;
- this.scrollDueToTouchEvent(e);
- }
- }, { passive: false });
- this._sliderTouchMoveListener = dom.addDisposableListener(this._domNode.domNode, EventType.Change, (e) => {
- e.preventDefault();
- e.stopPropagation();
- if (this._lastRenderData && this._gestureInProgress) {
- this.scrollDueToTouchEvent(e);
- }
- }, { passive: false });
- this._sliderTouchEndListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.End, (e) => {
- e.preventDefault();
- e.stopPropagation();
- this._gestureInProgress = false;
- this._slider.toggleClassName('active', false);
- });
- }
- _startSliderDragging(e, initialPosY, initialSliderState) {
- if (!e.target || !(e.target instanceof Element)) {
- return;
- }
- const initialPosX = e.pageX;
- this._slider.toggleClassName('active', true);
- const handlePointerMove = (posy, posx) => {
- const pointerOrthogonalDelta = Math.abs(posx - initialPosX);
- if (platform.isWindows && pointerOrthogonalDelta > POINTER_DRAG_RESET_DISTANCE) {
- // The pointer has wondered away from the scrollbar => reset dragging
- this._model.setScrollTop(initialSliderState.scrollTop);
- return;
- }
- const pointerDelta = posy - initialPosY;
- this._model.setScrollTop(initialSliderState.getDesiredScrollTopFromDelta(pointerDelta));
- };
- if (e.pageY !== initialPosY) {
- handlePointerMove(e.pageY, initialPosX);
- }
- this._sliderPointerMoveMonitor.startMonitoring(e.target, e.pointerId, e.buttons, pointerMoveData => handlePointerMove(pointerMoveData.pageY, pointerMoveData.pageX), () => {
- this._slider.toggleClassName('active', false);
- });
- }
- scrollDueToTouchEvent(touch) {
- const startY = this._domNode.domNode.getBoundingClientRect().top;
- const scrollTop = this._lastRenderData.renderedLayout.getDesiredScrollTopFromTouchLocation(touch.pageY - startY);
- this._model.setScrollTop(scrollTop);
- }
- dispose() {
- this._pointerDownListener.dispose();
- this._sliderPointerMoveMonitor.dispose();
- this._sliderPointerDownListener.dispose();
- this._gestureDisposable.dispose();
- this._sliderTouchStartListener.dispose();
- this._sliderTouchMoveListener.dispose();
- this._sliderTouchEndListener.dispose();
- super.dispose();
- }
- _getMinimapDomNodeClassName() {
- const class_ = ['minimap'];
- if (this._model.options.showSlider === 'always') {
- class_.push('slider-always');
- }
- else {
- class_.push('slider-mouseover');
- }
- if (this._model.options.autohide) {
- class_.push('autohide');
- }
- return class_.join(' ');
- }
- getDomNode() {
- return this._domNode;
- }
- _applyLayout() {
- this._domNode.setLeft(this._model.options.minimapLeft);
- this._domNode.setWidth(this._model.options.minimapWidth);
- this._domNode.setHeight(this._model.options.minimapHeight);
- this._shadow.setHeight(this._model.options.minimapHeight);
- this._canvas.setWidth(this._model.options.canvasOuterWidth);
- this._canvas.setHeight(this._model.options.canvasOuterHeight);
- this._canvas.domNode.width = this._model.options.canvasInnerWidth;
- this._canvas.domNode.height = this._model.options.canvasInnerHeight;
- this._decorationsCanvas.setWidth(this._model.options.canvasOuterWidth);
- this._decorationsCanvas.setHeight(this._model.options.canvasOuterHeight);
- this._decorationsCanvas.domNode.width = this._model.options.canvasInnerWidth;
- this._decorationsCanvas.domNode.height = this._model.options.canvasInnerHeight;
- this._slider.setWidth(this._model.options.minimapWidth);
- }
- _getBuffer() {
- if (!this._buffers) {
- if (this._model.options.canvasInnerWidth > 0 && this._model.options.canvasInnerHeight > 0) {
- this._buffers = new MinimapBuffers(this._canvas.domNode.getContext('2d'), this._model.options.canvasInnerWidth, this._model.options.canvasInnerHeight, this._model.options.backgroundColor);
- }
- }
- return this._buffers ? this._buffers.getBuffer() : null;
- }
- // ---- begin view event handlers
- onDidChangeOptions() {
- this._lastRenderData = null;
- this._buffers = null;
- this._applyLayout();
- this._domNode.setClassName(this._getMinimapDomNodeClassName());
- }
- onSelectionChanged() {
- this._renderDecorations = true;
- return true;
- }
- onDecorationsChanged() {
- this._renderDecorations = true;
- return true;
- }
- onFlushed() {
- this._lastRenderData = null;
- return true;
- }
- onLinesChanged(changeFromLineNumber, changeCount) {
- if (this._lastRenderData) {
- return this._lastRenderData.onLinesChanged(changeFromLineNumber, changeCount);
- }
- return false;
- }
- onLinesDeleted(deleteFromLineNumber, deleteToLineNumber) {
- var _a;
- (_a = this._lastRenderData) === null || _a === void 0 ? void 0 : _a.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber);
- return true;
- }
- onLinesInserted(insertFromLineNumber, insertToLineNumber) {
- var _a;
- (_a = this._lastRenderData) === null || _a === void 0 ? void 0 : _a.onLinesInserted(insertFromLineNumber, insertToLineNumber);
- return true;
- }
- onScrollChanged() {
- this._renderDecorations = true;
- return true;
- }
- onThemeChanged() {
- this._selectionColor = this._theme.getColor(minimapSelection);
- this._renderDecorations = true;
- return true;
- }
- onTokensChanged(ranges) {
- if (this._lastRenderData) {
- return this._lastRenderData.onTokensChanged(ranges);
- }
- return false;
- }
- onTokensColorsChanged() {
- this._lastRenderData = null;
- this._buffers = null;
- return true;
- }
- onZonesChanged() {
- this._lastRenderData = null;
- return true;
- }
- // --- end event handlers
- render(renderingCtx) {
- const renderMinimap = this._model.options.renderMinimap;
- if (renderMinimap === 0 /* RenderMinimap.None */) {
- this._shadow.setClassName('minimap-shadow-hidden');
- this._sliderHorizontal.setWidth(0);
- this._sliderHorizontal.setHeight(0);
- return;
- }
- if (renderingCtx.scrollLeft + renderingCtx.viewportWidth >= renderingCtx.scrollWidth) {
- this._shadow.setClassName('minimap-shadow-hidden');
- }
- else {
- this._shadow.setClassName('minimap-shadow-visible');
- }
- const layout = MinimapLayout.create(this._model.options, renderingCtx.viewportStartLineNumber, renderingCtx.viewportEndLineNumber, renderingCtx.viewportStartLineNumberVerticalOffset, renderingCtx.viewportHeight, renderingCtx.viewportContainsWhitespaceGaps, this._model.getLineCount(), this._model.getRealLineCount(), renderingCtx.scrollTop, renderingCtx.scrollHeight, this._lastRenderData ? this._lastRenderData.renderedLayout : null);
- this._slider.setDisplay(layout.sliderNeeded ? 'block' : 'none');
- this._slider.setTop(layout.sliderTop);
- this._slider.setHeight(layout.sliderHeight);
- // Compute horizontal slider coordinates
- this._sliderHorizontal.setLeft(0);
- this._sliderHorizontal.setWidth(this._model.options.minimapWidth);
- this._sliderHorizontal.setTop(0);
- this._sliderHorizontal.setHeight(layout.sliderHeight);
- this.renderDecorations(layout);
- this._lastRenderData = this.renderLines(layout);
- }
- renderDecorations(layout) {
- if (this._renderDecorations) {
- this._renderDecorations = false;
- const selections = this._model.getSelections();
- selections.sort(Range.compareRangesUsingStarts);
- const decorations = this._model.getMinimapDecorationsInViewport(layout.startLineNumber, layout.endLineNumber);
- decorations.sort((a, b) => (a.options.zIndex || 0) - (b.options.zIndex || 0));
- const { canvasInnerWidth, canvasInnerHeight } = this._model.options;
- const lineHeight = this._model.options.minimapLineHeight;
- const characterWidth = this._model.options.minimapCharWidth;
- const tabSize = this._model.getOptions().tabSize;
- const canvasContext = this._decorationsCanvas.domNode.getContext('2d');
- canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight);
- // We first need to render line highlights and then render decorations on top of those.
- // But we need to pick a single color for each line, and use that as a line highlight.
- // This needs to be the color of the decoration with the highest `zIndex`, but priority
- // is given to the selection.
- const highlightedLines = new ContiguousLineMap(layout.startLineNumber, layout.endLineNumber, false);
- this._renderSelectionLineHighlights(canvasContext, selections, highlightedLines, layout, lineHeight);
- this._renderDecorationsLineHighlights(canvasContext, decorations, highlightedLines, layout, lineHeight);
- const lineOffsetMap = new ContiguousLineMap(layout.startLineNumber, layout.endLineNumber, null);
- this._renderSelectionsHighlights(canvasContext, selections, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth);
- this._renderDecorationsHighlights(canvasContext, decorations, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth);
- }
- }
- _renderSelectionLineHighlights(canvasContext, selections, highlightedLines, layout, lineHeight) {
- if (!this._selectionColor || this._selectionColor.isTransparent()) {
- return;
- }
- canvasContext.fillStyle = this._selectionColor.transparent(0.5).toString();
- let y1 = 0;
- let y2 = 0;
- for (const selection of selections) {
- const startLineNumber = Math.max(layout.startLineNumber, selection.startLineNumber);
- const endLineNumber = Math.min(layout.endLineNumber, selection.endLineNumber);
- if (startLineNumber > endLineNumber) {
- // entirely outside minimap's viewport
- continue;
- }
- for (let line = startLineNumber; line <= endLineNumber; line++) {
- highlightedLines.set(line, true);
- }
- const yy1 = (startLineNumber - layout.startLineNumber) * lineHeight;
- const yy2 = (endLineNumber - layout.startLineNumber) * lineHeight + lineHeight;
- if (y2 >= yy1) {
- // merge into previous
- y2 = yy2;
- }
- else {
- if (y2 > y1) {
- // flush
- canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y1, canvasContext.canvas.width, y2 - y1);
- }
- y1 = yy1;
- y2 = yy2;
- }
- }
- if (y2 > y1) {
- // flush
- canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y1, canvasContext.canvas.width, y2 - y1);
- }
- }
- _renderDecorationsLineHighlights(canvasContext, decorations, highlightedLines, layout, lineHeight) {
- const highlightColors = new Map();
- // Loop backwards to hit first decorations with higher `zIndex`
- for (let i = decorations.length - 1; i >= 0; i--) {
- const decoration = decorations[i];
- const minimapOptions = decoration.options.minimap;
- if (!minimapOptions || minimapOptions.position !== MinimapPosition.Inline) {
- continue;
- }
- const startLineNumber = Math.max(layout.startLineNumber, decoration.range.startLineNumber);
- const endLineNumber = Math.min(layout.endLineNumber, decoration.range.endLineNumber);
- if (startLineNumber > endLineNumber) {
- // entirely outside minimap's viewport
- continue;
- }
- const decorationColor = minimapOptions.getColor(this._theme.value);
- if (!decorationColor || decorationColor.isTransparent()) {
- continue;
- }
- let highlightColor = highlightColors.get(decorationColor.toString());
- if (!highlightColor) {
- highlightColor = decorationColor.transparent(0.5).toString();
- highlightColors.set(decorationColor.toString(), highlightColor);
- }
- canvasContext.fillStyle = highlightColor;
- for (let line = startLineNumber; line <= endLineNumber; line++) {
- if (highlightedLines.has(line)) {
- continue;
- }
- highlightedLines.set(line, true);
- const y = (startLineNumber - layout.startLineNumber) * lineHeight;
- canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y, canvasContext.canvas.width, lineHeight);
- }
- }
- }
- _renderSelectionsHighlights(canvasContext, selections, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth) {
- if (!this._selectionColor || this._selectionColor.isTransparent()) {
- return;
- }
- for (const selection of selections) {
- const startLineNumber = Math.max(layout.startLineNumber, selection.startLineNumber);
- const endLineNumber = Math.min(layout.endLineNumber, selection.endLineNumber);
- if (startLineNumber > endLineNumber) {
- // entirely outside minimap's viewport
- continue;
- }
- for (let line = startLineNumber; line <= endLineNumber; line++) {
- this.renderDecorationOnLine(canvasContext, lineOffsetMap, selection, this._selectionColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth, canvasInnerWidth);
- }
- }
- }
- _renderDecorationsHighlights(canvasContext, decorations, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth) {
- // Loop forwards to hit first decorations with lower `zIndex`
- for (const decoration of decorations) {
- const minimapOptions = decoration.options.minimap;
- if (!minimapOptions) {
- continue;
- }
- const startLineNumber = Math.max(layout.startLineNumber, decoration.range.startLineNumber);
- const endLineNumber = Math.min(layout.endLineNumber, decoration.range.endLineNumber);
- if (startLineNumber > endLineNumber) {
- // entirely outside minimap's viewport
- continue;
- }
- const decorationColor = minimapOptions.getColor(this._theme.value);
- if (!decorationColor || decorationColor.isTransparent()) {
- continue;
- }
- for (let line = startLineNumber; line <= endLineNumber; line++) {
- switch (minimapOptions.position) {
- case MinimapPosition.Inline:
- this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth, canvasInnerWidth);
- continue;
- case MinimapPosition.Gutter: {
- const y = (line - layout.startLineNumber) * lineHeight;
- const x = 2;
- this.renderDecoration(canvasContext, decorationColor, x, y, GUTTER_DECORATION_WIDTH, lineHeight);
- continue;
- }
- }
- }
- }
- }
- renderDecorationOnLine(canvasContext, lineOffsetMap, decorationRange, decorationColor, layout, lineNumber, height, lineHeight, tabSize, charWidth, canvasInnerWidth) {
- const y = (lineNumber - layout.startLineNumber) * lineHeight;
- // Skip rendering the line if it's vertically outside our viewport
- if (y + height < 0 || y > this._model.options.canvasInnerHeight) {
- return;
- }
- const { startLineNumber, endLineNumber } = decorationRange;
- const startColumn = (startLineNumber === lineNumber ? decorationRange.startColumn : 1);
- const endColumn = (endLineNumber === lineNumber ? decorationRange.endColumn : this._model.getLineMaxColumn(lineNumber));
- const x1 = this.getXOffsetForPosition(lineOffsetMap, lineNumber, startColumn, tabSize, charWidth, canvasInnerWidth);
- const x2 = this.getXOffsetForPosition(lineOffsetMap, lineNumber, endColumn, tabSize, charWidth, canvasInnerWidth);
- this.renderDecoration(canvasContext, decorationColor, x1, y, x2 - x1, height);
- }
- getXOffsetForPosition(lineOffsetMap, lineNumber, column, tabSize, charWidth, canvasInnerWidth) {
- if (column === 1) {
- return MINIMAP_GUTTER_WIDTH;
- }
- const minimumXOffset = (column - 1) * charWidth;
- if (minimumXOffset >= canvasInnerWidth) {
- // there is no need to look at actual characters,
- // as this column is certainly after the minimap width
- return canvasInnerWidth;
- }
- // Cache line offset data so that it is only read once per line
- let lineIndexToXOffset = lineOffsetMap.get(lineNumber);
- if (!lineIndexToXOffset) {
- const lineData = this._model.getLineContent(lineNumber);
- lineIndexToXOffset = [MINIMAP_GUTTER_WIDTH];
- let prevx = MINIMAP_GUTTER_WIDTH;
- for (let i = 1; i < lineData.length + 1; i++) {
- const charCode = lineData.charCodeAt(i - 1);
- const dx = charCode === 9 /* CharCode.Tab */
- ? tabSize * charWidth
- : strings.isFullWidthCharacter(charCode)
- ? 2 * charWidth
- : charWidth;
- const x = prevx + dx;
- if (x >= canvasInnerWidth) {
- // no need to keep on going, as we've hit the canvas width
- lineIndexToXOffset[i] = canvasInnerWidth;
- break;
- }
- lineIndexToXOffset[i] = x;
- prevx = x;
- }
- lineOffsetMap.set(lineNumber, lineIndexToXOffset);
- }
- if (column - 1 < lineIndexToXOffset.length) {
- return lineIndexToXOffset[column - 1];
- }
- // goes over the canvas width
- return canvasInnerWidth;
- }
- renderDecoration(canvasContext, decorationColor, x, y, width, height) {
- canvasContext.fillStyle = decorationColor && decorationColor.toString() || '';
- canvasContext.fillRect(x, y, width, height);
- }
- renderLines(layout) {
- const startLineNumber = layout.startLineNumber;
- const endLineNumber = layout.endLineNumber;
- const minimapLineHeight = this._model.options.minimapLineHeight;
- // Check if nothing changed w.r.t. lines from last frame
- if (this._lastRenderData && this._lastRenderData.linesEquals(layout)) {
- const _lastData = this._lastRenderData._get();
- // Nice!! Nothing changed from last frame
- return new RenderData(layout, _lastData.imageData, _lastData.lines);
- }
- // Oh well!! We need to repaint some lines...
- const imageData = this._getBuffer();
- if (!imageData) {
- // 0 width or 0 height canvas, nothing to do
- return null;
- }
- // Render untouched lines by using last rendered data.
- const [_dirtyY1, _dirtyY2, needed] = InnerMinimap._renderUntouchedLines(imageData, startLineNumber, endLineNumber, minimapLineHeight, this._lastRenderData);
- // Fetch rendering info from view model for rest of lines that need rendering.
- const lineInfo = this._model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed);
- const tabSize = this._model.getOptions().tabSize;
- const defaultBackground = this._model.options.defaultBackgroundColor;
- const background = this._model.options.backgroundColor;
- const foregroundAlpha = this._model.options.foregroundAlpha;
- const tokensColorTracker = this._model.tokensColorTracker;
- const useLighterFont = tokensColorTracker.backgroundIsLight();
- const renderMinimap = this._model.options.renderMinimap;
- const charRenderer = this._model.options.charRenderer();
- const fontScale = this._model.options.fontScale;
- const minimapCharWidth = this._model.options.minimapCharWidth;
- const baseCharHeight = (renderMinimap === 1 /* RenderMinimap.Text */ ? 2 /* Constants.BASE_CHAR_HEIGHT */ : 2 /* Constants.BASE_CHAR_HEIGHT */ + 1);
- const renderMinimapLineHeight = baseCharHeight * fontScale;
- const innerLinePadding = (minimapLineHeight > renderMinimapLineHeight ? Math.floor((minimapLineHeight - renderMinimapLineHeight) / 2) : 0);
- // Render the rest of lines
- const backgroundA = background.a / 255;
- const renderBackground = new RGBA8(Math.round((background.r - defaultBackground.r) * backgroundA + defaultBackground.r), Math.round((background.g - defaultBackground.g) * backgroundA + defaultBackground.g), Math.round((background.b - defaultBackground.b) * backgroundA + defaultBackground.b), 255);
- let dy = 0;
- const renderedLines = [];
- for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) {
- if (needed[lineIndex]) {
- InnerMinimap._renderLine(imageData, renderBackground, background.a, useLighterFont, renderMinimap, minimapCharWidth, tokensColorTracker, foregroundAlpha, charRenderer, dy, innerLinePadding, tabSize, lineInfo[lineIndex], fontScale, minimapLineHeight);
- }
- renderedLines[lineIndex] = new MinimapLine(dy);
- dy += minimapLineHeight;
- }
- const dirtyY1 = (_dirtyY1 === -1 ? 0 : _dirtyY1);
- const dirtyY2 = (_dirtyY2 === -1 ? imageData.height : _dirtyY2);
- const dirtyHeight = dirtyY2 - dirtyY1;
- // Finally, paint to the canvas
- const ctx = this._canvas.domNode.getContext('2d');
- ctx.putImageData(imageData, 0, 0, 0, dirtyY1, imageData.width, dirtyHeight);
- // Save rendered data for reuse on next frame if possible
- return new RenderData(layout, imageData, renderedLines);
- }
- static _renderUntouchedLines(target, startLineNumber, endLineNumber, minimapLineHeight, lastRenderData) {
- const needed = [];
- if (!lastRenderData) {
- for (let i = 0, len = endLineNumber - startLineNumber + 1; i < len; i++) {
- needed[i] = true;
- }
- return [-1, -1, needed];
- }
- const _lastData = lastRenderData._get();
- const lastTargetData = _lastData.imageData.data;
- const lastStartLineNumber = _lastData.rendLineNumberStart;
- const lastLines = _lastData.lines;
- const lastLinesLength = lastLines.length;
- const WIDTH = target.width;
- const targetData = target.data;
- const maxDestPixel = (endLineNumber - startLineNumber + 1) * minimapLineHeight * WIDTH * 4;
- let dirtyPixel1 = -1; // the pixel offset up to which all the data is equal to the prev frame
- let dirtyPixel2 = -1; // the pixel offset after which all the data is equal to the prev frame
- let copySourceStart = -1;
- let copySourceEnd = -1;
- let copyDestStart = -1;
- let copyDestEnd = -1;
- let dest_dy = 0;
- for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
- const lineIndex = lineNumber - startLineNumber;
- const lastLineIndex = lineNumber - lastStartLineNumber;
- const source_dy = (lastLineIndex >= 0 && lastLineIndex < lastLinesLength ? lastLines[lastLineIndex].dy : -1);
- if (source_dy === -1) {
- needed[lineIndex] = true;
- dest_dy += minimapLineHeight;
- continue;
- }
- const sourceStart = source_dy * WIDTH * 4;
- const sourceEnd = (source_dy + minimapLineHeight) * WIDTH * 4;
- const destStart = dest_dy * WIDTH * 4;
- const destEnd = (dest_dy + minimapLineHeight) * WIDTH * 4;
- if (copySourceEnd === sourceStart && copyDestEnd === destStart) {
- // contiguous zone => extend copy request
- copySourceEnd = sourceEnd;
- copyDestEnd = destEnd;
- }
- else {
- if (copySourceStart !== -1) {
- // flush existing copy request
- targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
- if (dirtyPixel1 === -1 && copySourceStart === 0 && copySourceStart === copyDestStart) {
- dirtyPixel1 = copySourceEnd;
- }
- if (dirtyPixel2 === -1 && copySourceEnd === maxDestPixel && copySourceStart === copyDestStart) {
- dirtyPixel2 = copySourceStart;
- }
- }
- copySourceStart = sourceStart;
- copySourceEnd = sourceEnd;
- copyDestStart = destStart;
- copyDestEnd = destEnd;
- }
- needed[lineIndex] = false;
- dest_dy += minimapLineHeight;
- }
- if (copySourceStart !== -1) {
- // flush existing copy request
- targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
- if (dirtyPixel1 === -1 && copySourceStart === 0 && copySourceStart === copyDestStart) {
- dirtyPixel1 = copySourceEnd;
- }
- if (dirtyPixel2 === -1 && copySourceEnd === maxDestPixel && copySourceStart === copyDestStart) {
- dirtyPixel2 = copySourceStart;
- }
- }
- const dirtyY1 = (dirtyPixel1 === -1 ? -1 : dirtyPixel1 / (WIDTH * 4));
- const dirtyY2 = (dirtyPixel2 === -1 ? -1 : dirtyPixel2 / (WIDTH * 4));
- return [dirtyY1, dirtyY2, needed];
- }
- static _renderLine(target, backgroundColor, backgroundAlpha, useLighterFont, renderMinimap, charWidth, colorTracker, foregroundAlpha, minimapCharRenderer, dy, innerLinePadding, tabSize, lineData, fontScale, minimapLineHeight) {
- const content = lineData.content;
- const tokens = lineData.tokens;
- const maxDx = target.width - charWidth;
- const force1pxHeight = (minimapLineHeight === 1);
- let dx = MINIMAP_GUTTER_WIDTH;
- let charIndex = 0;
- let tabsCharDelta = 0;
- for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
- const tokenEndIndex = tokens.getEndOffset(tokenIndex);
- const tokenColorId = tokens.getForeground(tokenIndex);
- const tokenColor = colorTracker.getColor(tokenColorId);
- for (; charIndex < tokenEndIndex; charIndex++) {
- if (dx > maxDx) {
- // hit edge of minimap
- return;
- }
- const charCode = content.charCodeAt(charIndex);
- if (charCode === 9 /* CharCode.Tab */) {
- const insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
- tabsCharDelta += insertSpacesCount - 1;
- // No need to render anything since tab is invisible
- dx += insertSpacesCount * charWidth;
- }
- else if (charCode === 32 /* CharCode.Space */) {
- // No need to render anything since space is invisible
- dx += charWidth;
- }
- else {
- // Render twice for a full width character
- const count = strings.isFullWidthCharacter(charCode) ? 2 : 1;
- for (let i = 0; i < count; i++) {
- if (renderMinimap === 2 /* RenderMinimap.Blocks */) {
- minimapCharRenderer.blockRenderChar(target, dx, dy + innerLinePadding, tokenColor, foregroundAlpha, backgroundColor, backgroundAlpha, force1pxHeight);
- }
- else { // RenderMinimap.Text
- minimapCharRenderer.renderChar(target, dx, dy + innerLinePadding, charCode, tokenColor, foregroundAlpha, backgroundColor, backgroundAlpha, fontScale, useLighterFont, force1pxHeight);
- }
- dx += charWidth;
- if (dx > maxDx) {
- // hit edge of minimap
- return;
- }
- }
- }
- }
- }
- }
- }
- class ContiguousLineMap {
- constructor(startLineNumber, endLineNumber, defaultValue) {
- this._startLineNumber = startLineNumber;
- this._endLineNumber = endLineNumber;
- this._defaultValue = defaultValue;
- this._values = [];
- for (let i = 0, count = this._endLineNumber - this._startLineNumber + 1; i < count; i++) {
- this._values[i] = defaultValue;
- }
- }
- has(lineNumber) {
- return (this.get(lineNumber) !== this._defaultValue);
- }
- set(lineNumber, value) {
- if (lineNumber < this._startLineNumber || lineNumber > this._endLineNumber) {
- return;
- }
- this._values[lineNumber - this._startLineNumber] = value;
- }
- get(lineNumber) {
- if (lineNumber < this._startLineNumber || lineNumber > this._endLineNumber) {
- return this._defaultValue;
- }
- return this._values[lineNumber - this._startLineNumber];
- }
- }
- registerThemingParticipant((theme, collector) => {
- const sliderBackground = theme.getColor(minimapSliderBackground);
- if (sliderBackground) {
- collector.addRule(`.monaco-editor .minimap-slider .minimap-slider-horizontal { background: ${sliderBackground}; }`);
- }
- const sliderHoverBackground = theme.getColor(minimapSliderHoverBackground);
- if (sliderHoverBackground) {
- collector.addRule(`.monaco-editor .minimap-slider:hover .minimap-slider-horizontal { background: ${sliderHoverBackground}; }`);
- }
- const sliderActiveBackground = theme.getColor(minimapSliderActiveBackground);
- if (sliderActiveBackground) {
- collector.addRule(`.monaco-editor .minimap-slider.active .minimap-slider-horizontal { background: ${sliderActiveBackground}; }`);
- }
- const shadow = theme.getColor(scrollbarShadow);
- if (shadow) {
- collector.addRule(`.monaco-editor .minimap-shadow-visible { box-shadow: ${shadow} -6px 0 6px -6px inset; }`);
- }
- });
|