6532ea1276c8ca95061cca5a2fdbdc71484aabc40fdcaeb67a74da55ba102ba0c3785ea8354bbc486a43e7e21dccaf176d5e3fff8a780f5d5e9e1fc45b2b87 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import './minimap.css';
  6. import * as dom from '../../../../base/browser/dom.js';
  7. import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
  8. import { GlobalPointerMoveMonitor } from '../../../../base/browser/globalPointerMoveMonitor.js';
  9. import { Disposable } from '../../../../base/common/lifecycle.js';
  10. import * as platform from '../../../../base/common/platform.js';
  11. import * as strings from '../../../../base/common/strings.js';
  12. import { RenderedLinesCollection } from '../../view/viewLayer.js';
  13. import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
  14. import { MINIMAP_GUTTER_WIDTH, EditorLayoutInfoComputer } from '../../../common/config/editorOptions.js';
  15. import { Range } from '../../../common/core/range.js';
  16. import { RGBA8 } from '../../../common/core/rgba.js';
  17. import { MinimapTokensColorTracker } from '../../../common/viewModel/minimapTokensColorTracker.js';
  18. import { ViewModelDecoration } from '../../../common/viewModel.js';
  19. import { minimapSelection, scrollbarShadow, minimapBackground, minimapSliderBackground, minimapSliderHoverBackground, minimapSliderActiveBackground, minimapForegroundOpacity } from '../../../../platform/theme/common/colorRegistry.js';
  20. import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
  21. import { Selection } from '../../../common/core/selection.js';
  22. import { EventType, Gesture } from '../../../../base/browser/touch.js';
  23. import { MinimapCharRendererFactory } from './minimapCharRendererFactory.js';
  24. import { MinimapPosition } from '../../../common/model.js';
  25. import { once } from '../../../../base/common/functional.js';
  26. /**
  27. * The orthogonal distance to the slider at which dragging "resets". This implements "snapping"
  28. */
  29. const POINTER_DRAG_RESET_DISTANCE = 140;
  30. const GUTTER_DECORATION_WIDTH = 2;
  31. class MinimapOptions {
  32. constructor(configuration, theme, tokensColorTracker) {
  33. const options = configuration.options;
  34. const pixelRatio = options.get(131 /* EditorOption.pixelRatio */);
  35. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  36. const minimapLayout = layoutInfo.minimap;
  37. const fontInfo = options.get(46 /* EditorOption.fontInfo */);
  38. const minimapOpts = options.get(67 /* EditorOption.minimap */);
  39. this.renderMinimap = minimapLayout.renderMinimap;
  40. this.size = minimapOpts.size;
  41. this.minimapHeightIsEditorHeight = minimapLayout.minimapHeightIsEditorHeight;
  42. this.scrollBeyondLastLine = options.get(96 /* EditorOption.scrollBeyondLastLine */);
  43. this.showSlider = minimapOpts.showSlider;
  44. this.autohide = minimapOpts.autohide;
  45. this.pixelRatio = pixelRatio;
  46. this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
  47. this.lineHeight = options.get(61 /* EditorOption.lineHeight */);
  48. this.minimapLeft = minimapLayout.minimapLeft;
  49. this.minimapWidth = minimapLayout.minimapWidth;
  50. this.minimapHeight = layoutInfo.height;
  51. this.canvasInnerWidth = minimapLayout.minimapCanvasInnerWidth;
  52. this.canvasInnerHeight = minimapLayout.minimapCanvasInnerHeight;
  53. this.canvasOuterWidth = minimapLayout.minimapCanvasOuterWidth;
  54. this.canvasOuterHeight = minimapLayout.minimapCanvasOuterHeight;
  55. this.isSampling = minimapLayout.minimapIsSampling;
  56. this.editorHeight = layoutInfo.height;
  57. this.fontScale = minimapLayout.minimapScale;
  58. this.minimapLineHeight = minimapLayout.minimapLineHeight;
  59. this.minimapCharWidth = 1 /* Constants.BASE_CHAR_WIDTH */ * this.fontScale;
  60. this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily));
  61. this.defaultBackgroundColor = tokensColorTracker.getColor(2 /* ColorId.DefaultBackground */);
  62. this.backgroundColor = MinimapOptions._getMinimapBackground(theme, this.defaultBackgroundColor);
  63. this.foregroundAlpha = MinimapOptions._getMinimapForegroundOpacity(theme);
  64. }
  65. static _getMinimapBackground(theme, defaultBackgroundColor) {
  66. const themeColor = theme.getColor(minimapBackground);
  67. if (themeColor) {
  68. return new RGBA8(themeColor.rgba.r, themeColor.rgba.g, themeColor.rgba.b, Math.round(255 * themeColor.rgba.a));
  69. }
  70. return defaultBackgroundColor;
  71. }
  72. static _getMinimapForegroundOpacity(theme) {
  73. const themeColor = theme.getColor(minimapForegroundOpacity);
  74. if (themeColor) {
  75. return RGBA8._clamp(Math.round(255 * themeColor.rgba.a));
  76. }
  77. return 255;
  78. }
  79. equals(other) {
  80. return (this.renderMinimap === other.renderMinimap
  81. && this.size === other.size
  82. && this.minimapHeightIsEditorHeight === other.minimapHeightIsEditorHeight
  83. && this.scrollBeyondLastLine === other.scrollBeyondLastLine
  84. && this.showSlider === other.showSlider
  85. && this.autohide === other.autohide
  86. && this.pixelRatio === other.pixelRatio
  87. && this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth
  88. && this.lineHeight === other.lineHeight
  89. && this.minimapLeft === other.minimapLeft
  90. && this.minimapWidth === other.minimapWidth
  91. && this.minimapHeight === other.minimapHeight
  92. && this.canvasInnerWidth === other.canvasInnerWidth
  93. && this.canvasInnerHeight === other.canvasInnerHeight
  94. && this.canvasOuterWidth === other.canvasOuterWidth
  95. && this.canvasOuterHeight === other.canvasOuterHeight
  96. && this.isSampling === other.isSampling
  97. && this.editorHeight === other.editorHeight
  98. && this.fontScale === other.fontScale
  99. && this.minimapLineHeight === other.minimapLineHeight
  100. && this.minimapCharWidth === other.minimapCharWidth
  101. && this.defaultBackgroundColor && this.defaultBackgroundColor.equals(other.defaultBackgroundColor)
  102. && this.backgroundColor && this.backgroundColor.equals(other.backgroundColor)
  103. && this.foregroundAlpha === other.foregroundAlpha);
  104. }
  105. }
  106. class MinimapLayout {
  107. constructor(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber) {
  108. this.scrollTop = scrollTop;
  109. this.scrollHeight = scrollHeight;
  110. this.sliderNeeded = sliderNeeded;
  111. this._computedSliderRatio = computedSliderRatio;
  112. this.sliderTop = sliderTop;
  113. this.sliderHeight = sliderHeight;
  114. this.startLineNumber = startLineNumber;
  115. this.endLineNumber = endLineNumber;
  116. }
  117. /**
  118. * Compute a desired `scrollPosition` such that the slider moves by `delta`.
  119. */
  120. getDesiredScrollTopFromDelta(delta) {
  121. return Math.round(this.scrollTop + delta / this._computedSliderRatio);
  122. }
  123. getDesiredScrollTopFromTouchLocation(pageY) {
  124. return Math.round((pageY - this.sliderHeight / 2) / this._computedSliderRatio);
  125. }
  126. static create(options, viewportStartLineNumber, viewportEndLineNumber, viewportStartLineNumberVerticalOffset, viewportHeight, viewportContainsWhitespaceGaps, lineCount, realLineCount, scrollTop, scrollHeight, previousLayout) {
  127. const pixelRatio = options.pixelRatio;
  128. const minimapLineHeight = options.minimapLineHeight;
  129. const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight);
  130. const lineHeight = options.lineHeight;
  131. if (options.minimapHeightIsEditorHeight) {
  132. const logicalScrollHeight = (realLineCount * options.lineHeight
  133. + (options.scrollBeyondLastLine ? viewportHeight - options.lineHeight : 0));
  134. const sliderHeight = Math.max(1, Math.floor(viewportHeight * viewportHeight / logicalScrollHeight));
  135. const maxMinimapSliderTop = Math.max(0, options.minimapHeight - sliderHeight);
  136. // The slider can move from 0 to `maxMinimapSliderTop`
  137. // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`.
  138. const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight);
  139. const sliderTop = (scrollTop * computedSliderRatio);
  140. const sliderNeeded = (maxMinimapSliderTop > 0);
  141. const maxLinesFitting = Math.floor(options.canvasInnerHeight / options.minimapLineHeight);
  142. return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, Math.min(lineCount, maxLinesFitting));
  143. }
  144. // The visible line count in a viewport can change due to a number of reasons:
  145. // a) with the same viewport width, different scroll positions can result in partial lines being visible:
  146. // e.g. for a line height of 20, and a viewport height of 600
  147. // * scrollTop = 0 => visible lines are [1, 30]
  148. // * scrollTop = 10 => visible lines are [1, 31] (with lines 1 and 31 partially visible)
  149. // * scrollTop = 20 => visible lines are [2, 31]
  150. // b) whitespace gaps might make their way in the viewport (which results in a decrease in the visible line count)
  151. // 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)
  152. // We must first establish a desirable slider height.
  153. let sliderHeight;
  154. if (viewportContainsWhitespaceGaps && viewportEndLineNumber !== lineCount) {
  155. // case b) from above: there are whitespace gaps in the viewport.
  156. // In this case, the height of the slider directly reflects the visible line count.
  157. const viewportLineCount = viewportEndLineNumber - viewportStartLineNumber + 1;
  158. sliderHeight = Math.floor(viewportLineCount * minimapLineHeight / pixelRatio);
  159. }
  160. else {
  161. // The slider has a stable height
  162. const expectedViewportLineCount = viewportHeight / lineHeight;
  163. sliderHeight = Math.floor(expectedViewportLineCount * minimapLineHeight / pixelRatio);
  164. }
  165. let maxMinimapSliderTop;
  166. if (options.scrollBeyondLastLine) {
  167. // The minimap slider, when dragged all the way down, will contain the last line at its top
  168. maxMinimapSliderTop = (lineCount - 1) * minimapLineHeight / pixelRatio;
  169. }
  170. else {
  171. // The minimap slider, when dragged all the way down, will contain the last line at its bottom
  172. maxMinimapSliderTop = Math.max(0, lineCount * minimapLineHeight / pixelRatio - sliderHeight);
  173. }
  174. maxMinimapSliderTop = Math.min(options.minimapHeight - sliderHeight, maxMinimapSliderTop);
  175. // The slider can move from 0 to `maxMinimapSliderTop`
  176. // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`.
  177. const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight);
  178. const sliderTop = (scrollTop * computedSliderRatio);
  179. let extraLinesAtTheBottom = 0;
  180. if (options.scrollBeyondLastLine) {
  181. const expectedViewportLineCount = viewportHeight / lineHeight;
  182. extraLinesAtTheBottom = expectedViewportLineCount - 1;
  183. }
  184. if (minimapLinesFitting >= lineCount + extraLinesAtTheBottom) {
  185. // All lines fit in the minimap
  186. const startLineNumber = 1;
  187. const endLineNumber = lineCount;
  188. const sliderNeeded = (maxMinimapSliderTop > 0);
  189. return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber);
  190. }
  191. else {
  192. let startLineNumber = Math.max(1, Math.floor(viewportStartLineNumber - sliderTop * pixelRatio / minimapLineHeight));
  193. // Avoid flickering caused by a partial viewport start line
  194. // by being consistent w.r.t. the previous layout decision
  195. if (previousLayout && previousLayout.scrollHeight === scrollHeight) {
  196. if (previousLayout.scrollTop > scrollTop) {
  197. // Scrolling up => never increase `startLineNumber`
  198. startLineNumber = Math.min(startLineNumber, previousLayout.startLineNumber);
  199. }
  200. if (previousLayout.scrollTop < scrollTop) {
  201. // Scrolling down => never decrease `startLineNumber`
  202. startLineNumber = Math.max(startLineNumber, previousLayout.startLineNumber);
  203. }
  204. }
  205. const endLineNumber = Math.min(lineCount, startLineNumber + minimapLinesFitting - 1);
  206. const partialLine = (scrollTop - viewportStartLineNumberVerticalOffset) / lineHeight;
  207. const sliderTopAligned = (viewportStartLineNumber - startLineNumber + partialLine) * minimapLineHeight / pixelRatio;
  208. return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTopAligned, sliderHeight, startLineNumber, endLineNumber);
  209. }
  210. }
  211. }
  212. class MinimapLine {
  213. constructor(dy) {
  214. this.dy = dy;
  215. }
  216. onContentChanged() {
  217. this.dy = -1;
  218. }
  219. onTokensChanged() {
  220. this.dy = -1;
  221. }
  222. }
  223. MinimapLine.INVALID = new MinimapLine(-1);
  224. class RenderData {
  225. constructor(renderedLayout, imageData, lines) {
  226. this.renderedLayout = renderedLayout;
  227. this._imageData = imageData;
  228. this._renderedLines = new RenderedLinesCollection(() => MinimapLine.INVALID);
  229. this._renderedLines._set(renderedLayout.startLineNumber, lines);
  230. }
  231. /**
  232. * Check if the current RenderData matches accurately the new desired layout and no painting is needed.
  233. */
  234. linesEquals(layout) {
  235. if (!this.scrollEquals(layout)) {
  236. return false;
  237. }
  238. const tmp = this._renderedLines._get();
  239. const lines = tmp.lines;
  240. for (let i = 0, len = lines.length; i < len; i++) {
  241. if (lines[i].dy === -1) {
  242. // This line is invalid
  243. return false;
  244. }
  245. }
  246. return true;
  247. }
  248. /**
  249. * Check if the current RenderData matches the new layout's scroll position
  250. */
  251. scrollEquals(layout) {
  252. return this.renderedLayout.startLineNumber === layout.startLineNumber
  253. && this.renderedLayout.endLineNumber === layout.endLineNumber;
  254. }
  255. _get() {
  256. const tmp = this._renderedLines._get();
  257. return {
  258. imageData: this._imageData,
  259. rendLineNumberStart: tmp.rendLineNumberStart,
  260. lines: tmp.lines
  261. };
  262. }
  263. onLinesChanged(changeFromLineNumber, changeCount) {
  264. return this._renderedLines.onLinesChanged(changeFromLineNumber, changeCount);
  265. }
  266. onLinesDeleted(deleteFromLineNumber, deleteToLineNumber) {
  267. this._renderedLines.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber);
  268. }
  269. onLinesInserted(insertFromLineNumber, insertToLineNumber) {
  270. this._renderedLines.onLinesInserted(insertFromLineNumber, insertToLineNumber);
  271. }
  272. onTokensChanged(ranges) {
  273. return this._renderedLines.onTokensChanged(ranges);
  274. }
  275. }
  276. /**
  277. * Some sort of double buffering.
  278. *
  279. * Keeps two buffers around that will be rotated for painting.
  280. * Always gives a buffer that is filled with the background color.
  281. */
  282. class MinimapBuffers {
  283. constructor(ctx, WIDTH, HEIGHT, background) {
  284. this._backgroundFillData = MinimapBuffers._createBackgroundFillData(WIDTH, HEIGHT, background);
  285. this._buffers = [
  286. ctx.createImageData(WIDTH, HEIGHT),
  287. ctx.createImageData(WIDTH, HEIGHT)
  288. ];
  289. this._lastUsedBuffer = 0;
  290. }
  291. getBuffer() {
  292. // rotate buffers
  293. this._lastUsedBuffer = 1 - this._lastUsedBuffer;
  294. const result = this._buffers[this._lastUsedBuffer];
  295. // fill with background color
  296. result.data.set(this._backgroundFillData);
  297. return result;
  298. }
  299. static _createBackgroundFillData(WIDTH, HEIGHT, background) {
  300. const backgroundR = background.r;
  301. const backgroundG = background.g;
  302. const backgroundB = background.b;
  303. const backgroundA = background.a;
  304. const result = new Uint8ClampedArray(WIDTH * HEIGHT * 4);
  305. let offset = 0;
  306. for (let i = 0; i < HEIGHT; i++) {
  307. for (let j = 0; j < WIDTH; j++) {
  308. result[offset] = backgroundR;
  309. result[offset + 1] = backgroundG;
  310. result[offset + 2] = backgroundB;
  311. result[offset + 3] = backgroundA;
  312. offset += 4;
  313. }
  314. }
  315. return result;
  316. }
  317. }
  318. class MinimapSamplingState {
  319. constructor(samplingRatio, minimapLines) {
  320. this.samplingRatio = samplingRatio;
  321. this.minimapLines = minimapLines;
  322. }
  323. static compute(options, viewLineCount, oldSamplingState) {
  324. if (options.renderMinimap === 0 /* RenderMinimap.None */ || !options.isSampling) {
  325. return [null, []];
  326. }
  327. // ratio is intentionally not part of the layout to avoid the layout changing all the time
  328. // so we need to recompute it again...
  329. const pixelRatio = options.pixelRatio;
  330. const lineHeight = options.lineHeight;
  331. const scrollBeyondLastLine = options.scrollBeyondLastLine;
  332. const { minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
  333. viewLineCount: viewLineCount,
  334. scrollBeyondLastLine: scrollBeyondLastLine,
  335. height: options.editorHeight,
  336. lineHeight: lineHeight,
  337. pixelRatio: pixelRatio
  338. });
  339. const ratio = viewLineCount / minimapLineCount;
  340. const halfRatio = ratio / 2;
  341. if (!oldSamplingState || oldSamplingState.minimapLines.length === 0) {
  342. const result = [];
  343. result[0] = 1;
  344. if (minimapLineCount > 1) {
  345. for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) {
  346. result[i] = Math.round(i * ratio + halfRatio);
  347. }
  348. result[minimapLineCount - 1] = viewLineCount;
  349. }
  350. return [new MinimapSamplingState(ratio, result), []];
  351. }
  352. const oldMinimapLines = oldSamplingState.minimapLines;
  353. const oldLength = oldMinimapLines.length;
  354. const result = [];
  355. let oldIndex = 0;
  356. let oldDeltaLineCount = 0;
  357. let minViewLineNumber = 1;
  358. const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data
  359. let events = [];
  360. let lastEvent = null;
  361. for (let i = 0; i < minimapLineCount; i++) {
  362. const fromViewLineNumber = Math.max(minViewLineNumber, Math.round(i * ratio));
  363. const toViewLineNumber = Math.max(fromViewLineNumber, Math.round((i + 1) * ratio));
  364. while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromViewLineNumber) {
  365. if (events.length < MAX_EVENT_COUNT) {
  366. const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
  367. if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) {
  368. lastEvent.deleteToLineNumber++;
  369. }
  370. else {
  371. lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber };
  372. events.push(lastEvent);
  373. }
  374. oldDeltaLineCount--;
  375. }
  376. oldIndex++;
  377. }
  378. let selectedViewLineNumber;
  379. if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toViewLineNumber) {
  380. // reuse the old sampled line
  381. selectedViewLineNumber = oldMinimapLines[oldIndex];
  382. oldIndex++;
  383. }
  384. else {
  385. if (i === 0) {
  386. selectedViewLineNumber = 1;
  387. }
  388. else if (i + 1 === minimapLineCount) {
  389. selectedViewLineNumber = viewLineCount;
  390. }
  391. else {
  392. selectedViewLineNumber = Math.round(i * ratio + halfRatio);
  393. }
  394. if (events.length < MAX_EVENT_COUNT) {
  395. const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
  396. if (lastEvent && lastEvent.type === 'inserted' && lastEvent._i === i - 1) {
  397. lastEvent.insertToLineNumber++;
  398. }
  399. else {
  400. lastEvent = { type: 'inserted', _i: i, insertFromLineNumber: oldMinimapLineNumber, insertToLineNumber: oldMinimapLineNumber };
  401. events.push(lastEvent);
  402. }
  403. oldDeltaLineCount++;
  404. }
  405. }
  406. result[i] = selectedViewLineNumber;
  407. minViewLineNumber = selectedViewLineNumber;
  408. }
  409. if (events.length < MAX_EVENT_COUNT) {
  410. while (oldIndex < oldLength) {
  411. const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
  412. if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) {
  413. lastEvent.deleteToLineNumber++;
  414. }
  415. else {
  416. lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber };
  417. events.push(lastEvent);
  418. }
  419. oldDeltaLineCount--;
  420. oldIndex++;
  421. }
  422. }
  423. else {
  424. // too many events, just give up
  425. events = [{ type: 'flush' }];
  426. }
  427. return [new MinimapSamplingState(ratio, result), events];
  428. }
  429. modelLineToMinimapLine(lineNumber) {
  430. return Math.min(this.minimapLines.length, Math.max(1, Math.round(lineNumber / this.samplingRatio)));
  431. }
  432. /**
  433. * Will return null if the model line ranges are not intersecting with a sampled model line.
  434. */
  435. modelLineRangeToMinimapLineRange(fromLineNumber, toLineNumber) {
  436. let fromLineIndex = this.modelLineToMinimapLine(fromLineNumber) - 1;
  437. while (fromLineIndex > 0 && this.minimapLines[fromLineIndex - 1] >= fromLineNumber) {
  438. fromLineIndex--;
  439. }
  440. let toLineIndex = this.modelLineToMinimapLine(toLineNumber) - 1;
  441. while (toLineIndex + 1 < this.minimapLines.length && this.minimapLines[toLineIndex + 1] <= toLineNumber) {
  442. toLineIndex++;
  443. }
  444. if (fromLineIndex === toLineIndex) {
  445. const sampledLineNumber = this.minimapLines[fromLineIndex];
  446. if (sampledLineNumber < fromLineNumber || sampledLineNumber > toLineNumber) {
  447. // This line is not part of the sampled lines ==> nothing to do
  448. return null;
  449. }
  450. }
  451. return [fromLineIndex + 1, toLineIndex + 1];
  452. }
  453. /**
  454. * Will always return a range, even if it is not intersecting with a sampled model line.
  455. */
  456. decorationLineRangeToMinimapLineRange(startLineNumber, endLineNumber) {
  457. let minimapLineStart = this.modelLineToMinimapLine(startLineNumber);
  458. let minimapLineEnd = this.modelLineToMinimapLine(endLineNumber);
  459. if (startLineNumber !== endLineNumber && minimapLineEnd === minimapLineStart) {
  460. if (minimapLineEnd === this.minimapLines.length) {
  461. if (minimapLineStart > 1) {
  462. minimapLineStart--;
  463. }
  464. }
  465. else {
  466. minimapLineEnd++;
  467. }
  468. }
  469. return [minimapLineStart, minimapLineEnd];
  470. }
  471. onLinesDeleted(e) {
  472. // have the mapping be sticky
  473. const deletedLineCount = e.toLineNumber - e.fromLineNumber + 1;
  474. let changeStartIndex = this.minimapLines.length;
  475. let changeEndIndex = 0;
  476. for (let i = this.minimapLines.length - 1; i >= 0; i--) {
  477. if (this.minimapLines[i] < e.fromLineNumber) {
  478. break;
  479. }
  480. if (this.minimapLines[i] <= e.toLineNumber) {
  481. // this line got deleted => move to previous available
  482. this.minimapLines[i] = Math.max(1, e.fromLineNumber - 1);
  483. changeStartIndex = Math.min(changeStartIndex, i);
  484. changeEndIndex = Math.max(changeEndIndex, i);
  485. }
  486. else {
  487. this.minimapLines[i] -= deletedLineCount;
  488. }
  489. }
  490. return [changeStartIndex, changeEndIndex];
  491. }
  492. onLinesInserted(e) {
  493. // have the mapping be sticky
  494. const insertedLineCount = e.toLineNumber - e.fromLineNumber + 1;
  495. for (let i = this.minimapLines.length - 1; i >= 0; i--) {
  496. if (this.minimapLines[i] < e.fromLineNumber) {
  497. break;
  498. }
  499. this.minimapLines[i] += insertedLineCount;
  500. }
  501. }
  502. }
  503. export class Minimap extends ViewPart {
  504. constructor(context) {
  505. super(context);
  506. this.tokensColorTracker = MinimapTokensColorTracker.getInstance();
  507. this._selections = [];
  508. this._minimapSelections = null;
  509. this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker);
  510. const [samplingState,] = MinimapSamplingState.compute(this.options, this._context.viewModel.getLineCount(), null);
  511. this._samplingState = samplingState;
  512. this._shouldCheckSampling = false;
  513. this._actual = new InnerMinimap(context.theme, this);
  514. }
  515. dispose() {
  516. this._actual.dispose();
  517. super.dispose();
  518. }
  519. getDomNode() {
  520. return this._actual.getDomNode();
  521. }
  522. _onOptionsMaybeChanged() {
  523. const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker);
  524. if (this.options.equals(opts)) {
  525. return false;
  526. }
  527. this.options = opts;
  528. this._recreateLineSampling();
  529. this._actual.onDidChangeOptions();
  530. return true;
  531. }
  532. // ---- begin view event handlers
  533. onConfigurationChanged(e) {
  534. return this._onOptionsMaybeChanged();
  535. }
  536. onCursorStateChanged(e) {
  537. this._selections = e.selections;
  538. this._minimapSelections = null;
  539. return this._actual.onSelectionChanged();
  540. }
  541. onDecorationsChanged(e) {
  542. if (e.affectsMinimap) {
  543. return this._actual.onDecorationsChanged();
  544. }
  545. return false;
  546. }
  547. onFlushed(e) {
  548. if (this._samplingState) {
  549. this._shouldCheckSampling = true;
  550. }
  551. return this._actual.onFlushed();
  552. }
  553. onLinesChanged(e) {
  554. if (this._samplingState) {
  555. const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(e.fromLineNumber, e.fromLineNumber + e.count - 1);
  556. if (minimapLineRange) {
  557. return this._actual.onLinesChanged(minimapLineRange[0], minimapLineRange[1] - minimapLineRange[0] + 1);
  558. }
  559. else {
  560. return false;
  561. }
  562. }
  563. else {
  564. return this._actual.onLinesChanged(e.fromLineNumber, e.count);
  565. }
  566. }
  567. onLinesDeleted(e) {
  568. if (this._samplingState) {
  569. const [changeStartIndex, changeEndIndex] = this._samplingState.onLinesDeleted(e);
  570. if (changeStartIndex <= changeEndIndex) {
  571. this._actual.onLinesChanged(changeStartIndex + 1, changeEndIndex - changeStartIndex + 1);
  572. }
  573. this._shouldCheckSampling = true;
  574. return true;
  575. }
  576. else {
  577. return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber);
  578. }
  579. }
  580. onLinesInserted(e) {
  581. if (this._samplingState) {
  582. this._samplingState.onLinesInserted(e);
  583. this._shouldCheckSampling = true;
  584. return true;
  585. }
  586. else {
  587. return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber);
  588. }
  589. }
  590. onScrollChanged(e) {
  591. return this._actual.onScrollChanged();
  592. }
  593. onThemeChanged(e) {
  594. this._actual.onThemeChanged();
  595. this._onOptionsMaybeChanged();
  596. return true;
  597. }
  598. onTokensChanged(e) {
  599. if (this._samplingState) {
  600. const ranges = [];
  601. for (const range of e.ranges) {
  602. const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(range.fromLineNumber, range.toLineNumber);
  603. if (minimapLineRange) {
  604. ranges.push({ fromLineNumber: minimapLineRange[0], toLineNumber: minimapLineRange[1] });
  605. }
  606. }
  607. if (ranges.length) {
  608. return this._actual.onTokensChanged(ranges);
  609. }
  610. else {
  611. return false;
  612. }
  613. }
  614. else {
  615. return this._actual.onTokensChanged(e.ranges);
  616. }
  617. }
  618. onTokensColorsChanged(e) {
  619. this._onOptionsMaybeChanged();
  620. return this._actual.onTokensColorsChanged();
  621. }
  622. onZonesChanged(e) {
  623. return this._actual.onZonesChanged();
  624. }
  625. // --- end event handlers
  626. prepareRender(ctx) {
  627. if (this._shouldCheckSampling) {
  628. this._shouldCheckSampling = false;
  629. this._recreateLineSampling();
  630. }
  631. }
  632. render(ctx) {
  633. let viewportStartLineNumber = ctx.visibleRange.startLineNumber;
  634. let viewportEndLineNumber = ctx.visibleRange.endLineNumber;
  635. if (this._samplingState) {
  636. viewportStartLineNumber = this._samplingState.modelLineToMinimapLine(viewportStartLineNumber);
  637. viewportEndLineNumber = this._samplingState.modelLineToMinimapLine(viewportEndLineNumber);
  638. }
  639. const minimapCtx = {
  640. viewportContainsWhitespaceGaps: (ctx.viewportData.whitespaceViewportData.length > 0),
  641. scrollWidth: ctx.scrollWidth,
  642. scrollHeight: ctx.scrollHeight,
  643. viewportStartLineNumber: viewportStartLineNumber,
  644. viewportEndLineNumber: viewportEndLineNumber,
  645. viewportStartLineNumberVerticalOffset: ctx.getVerticalOffsetForLineNumber(viewportStartLineNumber),
  646. scrollTop: ctx.scrollTop,
  647. scrollLeft: ctx.scrollLeft,
  648. viewportWidth: ctx.viewportWidth,
  649. viewportHeight: ctx.viewportHeight,
  650. };
  651. this._actual.render(minimapCtx);
  652. }
  653. //#region IMinimapModel
  654. _recreateLineSampling() {
  655. this._minimapSelections = null;
  656. const wasSampling = Boolean(this._samplingState);
  657. const [samplingState, events] = MinimapSamplingState.compute(this.options, this._context.viewModel.getLineCount(), this._samplingState);
  658. this._samplingState = samplingState;
  659. if (wasSampling && this._samplingState) {
  660. // was sampling, is sampling
  661. for (const event of events) {
  662. switch (event.type) {
  663. case 'deleted':
  664. this._actual.onLinesDeleted(event.deleteFromLineNumber, event.deleteToLineNumber);
  665. break;
  666. case 'inserted':
  667. this._actual.onLinesInserted(event.insertFromLineNumber, event.insertToLineNumber);
  668. break;
  669. case 'flush':
  670. this._actual.onFlushed();
  671. break;
  672. }
  673. }
  674. }
  675. }
  676. getLineCount() {
  677. if (this._samplingState) {
  678. return this._samplingState.minimapLines.length;
  679. }
  680. return this._context.viewModel.getLineCount();
  681. }
  682. getRealLineCount() {
  683. return this._context.viewModel.getLineCount();
  684. }
  685. getLineContent(lineNumber) {
  686. if (this._samplingState) {
  687. return this._context.viewModel.getLineContent(this._samplingState.minimapLines[lineNumber - 1]);
  688. }
  689. return this._context.viewModel.getLineContent(lineNumber);
  690. }
  691. getLineMaxColumn(lineNumber) {
  692. if (this._samplingState) {
  693. return this._context.viewModel.getLineMaxColumn(this._samplingState.minimapLines[lineNumber - 1]);
  694. }
  695. return this._context.viewModel.getLineMaxColumn(lineNumber);
  696. }
  697. getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed) {
  698. if (this._samplingState) {
  699. const result = [];
  700. for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) {
  701. if (needed[lineIndex]) {
  702. result[lineIndex] = this._context.viewModel.getViewLineData(this._samplingState.minimapLines[startLineNumber + lineIndex - 1]);
  703. }
  704. else {
  705. result[lineIndex] = null;
  706. }
  707. }
  708. return result;
  709. }
  710. return this._context.viewModel.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed).data;
  711. }
  712. getSelections() {
  713. if (this._minimapSelections === null) {
  714. if (this._samplingState) {
  715. this._minimapSelections = [];
  716. for (const selection of this._selections) {
  717. const [minimapLineStart, minimapLineEnd] = this._samplingState.decorationLineRangeToMinimapLineRange(selection.startLineNumber, selection.endLineNumber);
  718. this._minimapSelections.push(new Selection(minimapLineStart, selection.startColumn, minimapLineEnd, selection.endColumn));
  719. }
  720. }
  721. else {
  722. this._minimapSelections = this._selections;
  723. }
  724. }
  725. return this._minimapSelections;
  726. }
  727. getMinimapDecorationsInViewport(startLineNumber, endLineNumber) {
  728. let visibleRange;
  729. if (this._samplingState) {
  730. const modelStartLineNumber = this._samplingState.minimapLines[startLineNumber - 1];
  731. const modelEndLineNumber = this._samplingState.minimapLines[endLineNumber - 1];
  732. visibleRange = new Range(modelStartLineNumber, 1, modelEndLineNumber, this._context.viewModel.getLineMaxColumn(modelEndLineNumber));
  733. }
  734. else {
  735. visibleRange = new Range(startLineNumber, 1, endLineNumber, this._context.viewModel.getLineMaxColumn(endLineNumber));
  736. }
  737. const decorations = this._context.viewModel.getDecorationsInViewport(visibleRange);
  738. if (this._samplingState) {
  739. const result = [];
  740. for (const decoration of decorations) {
  741. if (!decoration.options.minimap) {
  742. continue;
  743. }
  744. const range = decoration.range;
  745. const minimapStartLineNumber = this._samplingState.modelLineToMinimapLine(range.startLineNumber);
  746. const minimapEndLineNumber = this._samplingState.modelLineToMinimapLine(range.endLineNumber);
  747. result.push(new ViewModelDecoration(new Range(minimapStartLineNumber, range.startColumn, minimapEndLineNumber, range.endColumn), decoration.options));
  748. }
  749. return result;
  750. }
  751. return decorations;
  752. }
  753. getOptions() {
  754. return this._context.viewModel.model.getOptions();
  755. }
  756. revealLineNumber(lineNumber) {
  757. if (this._samplingState) {
  758. lineNumber = this._samplingState.minimapLines[lineNumber - 1];
  759. }
  760. this._context.viewModel.revealRange('mouse', false, new Range(lineNumber, 1, lineNumber, 1), 1 /* viewEvents.VerticalRevealType.Center */, 0 /* ScrollType.Smooth */);
  761. }
  762. setScrollTop(scrollTop) {
  763. this._context.viewModel.viewLayout.setScrollPosition({
  764. scrollTop: scrollTop
  765. }, 1 /* ScrollType.Immediate */);
  766. }
  767. }
  768. class InnerMinimap extends Disposable {
  769. constructor(theme, model) {
  770. super();
  771. this._renderDecorations = false;
  772. this._gestureInProgress = false;
  773. this._theme = theme;
  774. this._model = model;
  775. this._lastRenderData = null;
  776. this._buffers = null;
  777. this._selectionColor = this._theme.getColor(minimapSelection);
  778. this._domNode = createFastDomNode(document.createElement('div'));
  779. PartFingerprints.write(this._domNode, 8 /* PartFingerprint.Minimap */);
  780. this._domNode.setClassName(this._getMinimapDomNodeClassName());
  781. this._domNode.setPosition('absolute');
  782. this._domNode.setAttribute('role', 'presentation');
  783. this._domNode.setAttribute('aria-hidden', 'true');
  784. this._shadow = createFastDomNode(document.createElement('div'));
  785. this._shadow.setClassName('minimap-shadow-hidden');
  786. this._domNode.appendChild(this._shadow);
  787. this._canvas = createFastDomNode(document.createElement('canvas'));
  788. this._canvas.setPosition('absolute');
  789. this._canvas.setLeft(0);
  790. this._domNode.appendChild(this._canvas);
  791. this._decorationsCanvas = createFastDomNode(document.createElement('canvas'));
  792. this._decorationsCanvas.setPosition('absolute');
  793. this._decorationsCanvas.setClassName('minimap-decorations-layer');
  794. this._decorationsCanvas.setLeft(0);
  795. this._domNode.appendChild(this._decorationsCanvas);
  796. this._slider = createFastDomNode(document.createElement('div'));
  797. this._slider.setPosition('absolute');
  798. this._slider.setClassName('minimap-slider');
  799. this._slider.setLayerHinting(true);
  800. this._slider.setContain('strict');
  801. this._domNode.appendChild(this._slider);
  802. this._sliderHorizontal = createFastDomNode(document.createElement('div'));
  803. this._sliderHorizontal.setPosition('absolute');
  804. this._sliderHorizontal.setClassName('minimap-slider-horizontal');
  805. this._slider.appendChild(this._sliderHorizontal);
  806. this._applyLayout();
  807. this._pointerDownListener = dom.addStandardDisposableListener(this._domNode.domNode, dom.EventType.POINTER_DOWN, (e) => {
  808. e.preventDefault();
  809. const renderMinimap = this._model.options.renderMinimap;
  810. if (renderMinimap === 0 /* RenderMinimap.None */) {
  811. return;
  812. }
  813. if (!this._lastRenderData) {
  814. return;
  815. }
  816. if (this._model.options.size !== 'proportional') {
  817. if (e.button === 0 && this._lastRenderData) {
  818. // pretend the click occurred in the center of the slider
  819. const position = dom.getDomNodePagePosition(this._slider.domNode);
  820. const initialPosY = position.top + position.height / 2;
  821. this._startSliderDragging(e, initialPosY, this._lastRenderData.renderedLayout);
  822. }
  823. return;
  824. }
  825. const minimapLineHeight = this._model.options.minimapLineHeight;
  826. const internalOffsetY = (this._model.options.canvasInnerHeight / this._model.options.canvasOuterHeight) * e.offsetY;
  827. const lineIndex = Math.floor(internalOffsetY / minimapLineHeight);
  828. let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber;
  829. lineNumber = Math.min(lineNumber, this._model.getLineCount());
  830. this._model.revealLineNumber(lineNumber);
  831. });
  832. this._sliderPointerMoveMonitor = new GlobalPointerMoveMonitor();
  833. this._sliderPointerDownListener = dom.addStandardDisposableListener(this._slider.domNode, dom.EventType.POINTER_DOWN, (e) => {
  834. e.preventDefault();
  835. e.stopPropagation();
  836. if (e.button === 0 && this._lastRenderData) {
  837. this._startSliderDragging(e, e.pageY, this._lastRenderData.renderedLayout);
  838. }
  839. });
  840. this._gestureDisposable = Gesture.addTarget(this._domNode.domNode);
  841. this._sliderTouchStartListener = dom.addDisposableListener(this._domNode.domNode, EventType.Start, (e) => {
  842. e.preventDefault();
  843. e.stopPropagation();
  844. if (this._lastRenderData) {
  845. this._slider.toggleClassName('active', true);
  846. this._gestureInProgress = true;
  847. this.scrollDueToTouchEvent(e);
  848. }
  849. }, { passive: false });
  850. this._sliderTouchMoveListener = dom.addDisposableListener(this._domNode.domNode, EventType.Change, (e) => {
  851. e.preventDefault();
  852. e.stopPropagation();
  853. if (this._lastRenderData && this._gestureInProgress) {
  854. this.scrollDueToTouchEvent(e);
  855. }
  856. }, { passive: false });
  857. this._sliderTouchEndListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.End, (e) => {
  858. e.preventDefault();
  859. e.stopPropagation();
  860. this._gestureInProgress = false;
  861. this._slider.toggleClassName('active', false);
  862. });
  863. }
  864. _startSliderDragging(e, initialPosY, initialSliderState) {
  865. if (!e.target || !(e.target instanceof Element)) {
  866. return;
  867. }
  868. const initialPosX = e.pageX;
  869. this._slider.toggleClassName('active', true);
  870. const handlePointerMove = (posy, posx) => {
  871. const pointerOrthogonalDelta = Math.abs(posx - initialPosX);
  872. if (platform.isWindows && pointerOrthogonalDelta > POINTER_DRAG_RESET_DISTANCE) {
  873. // The pointer has wondered away from the scrollbar => reset dragging
  874. this._model.setScrollTop(initialSliderState.scrollTop);
  875. return;
  876. }
  877. const pointerDelta = posy - initialPosY;
  878. this._model.setScrollTop(initialSliderState.getDesiredScrollTopFromDelta(pointerDelta));
  879. };
  880. if (e.pageY !== initialPosY) {
  881. handlePointerMove(e.pageY, initialPosX);
  882. }
  883. this._sliderPointerMoveMonitor.startMonitoring(e.target, e.pointerId, e.buttons, pointerMoveData => handlePointerMove(pointerMoveData.pageY, pointerMoveData.pageX), () => {
  884. this._slider.toggleClassName('active', false);
  885. });
  886. }
  887. scrollDueToTouchEvent(touch) {
  888. const startY = this._domNode.domNode.getBoundingClientRect().top;
  889. const scrollTop = this._lastRenderData.renderedLayout.getDesiredScrollTopFromTouchLocation(touch.pageY - startY);
  890. this._model.setScrollTop(scrollTop);
  891. }
  892. dispose() {
  893. this._pointerDownListener.dispose();
  894. this._sliderPointerMoveMonitor.dispose();
  895. this._sliderPointerDownListener.dispose();
  896. this._gestureDisposable.dispose();
  897. this._sliderTouchStartListener.dispose();
  898. this._sliderTouchMoveListener.dispose();
  899. this._sliderTouchEndListener.dispose();
  900. super.dispose();
  901. }
  902. _getMinimapDomNodeClassName() {
  903. const class_ = ['minimap'];
  904. if (this._model.options.showSlider === 'always') {
  905. class_.push('slider-always');
  906. }
  907. else {
  908. class_.push('slider-mouseover');
  909. }
  910. if (this._model.options.autohide) {
  911. class_.push('autohide');
  912. }
  913. return class_.join(' ');
  914. }
  915. getDomNode() {
  916. return this._domNode;
  917. }
  918. _applyLayout() {
  919. this._domNode.setLeft(this._model.options.minimapLeft);
  920. this._domNode.setWidth(this._model.options.minimapWidth);
  921. this._domNode.setHeight(this._model.options.minimapHeight);
  922. this._shadow.setHeight(this._model.options.minimapHeight);
  923. this._canvas.setWidth(this._model.options.canvasOuterWidth);
  924. this._canvas.setHeight(this._model.options.canvasOuterHeight);
  925. this._canvas.domNode.width = this._model.options.canvasInnerWidth;
  926. this._canvas.domNode.height = this._model.options.canvasInnerHeight;
  927. this._decorationsCanvas.setWidth(this._model.options.canvasOuterWidth);
  928. this._decorationsCanvas.setHeight(this._model.options.canvasOuterHeight);
  929. this._decorationsCanvas.domNode.width = this._model.options.canvasInnerWidth;
  930. this._decorationsCanvas.domNode.height = this._model.options.canvasInnerHeight;
  931. this._slider.setWidth(this._model.options.minimapWidth);
  932. }
  933. _getBuffer() {
  934. if (!this._buffers) {
  935. if (this._model.options.canvasInnerWidth > 0 && this._model.options.canvasInnerHeight > 0) {
  936. this._buffers = new MinimapBuffers(this._canvas.domNode.getContext('2d'), this._model.options.canvasInnerWidth, this._model.options.canvasInnerHeight, this._model.options.backgroundColor);
  937. }
  938. }
  939. return this._buffers ? this._buffers.getBuffer() : null;
  940. }
  941. // ---- begin view event handlers
  942. onDidChangeOptions() {
  943. this._lastRenderData = null;
  944. this._buffers = null;
  945. this._applyLayout();
  946. this._domNode.setClassName(this._getMinimapDomNodeClassName());
  947. }
  948. onSelectionChanged() {
  949. this._renderDecorations = true;
  950. return true;
  951. }
  952. onDecorationsChanged() {
  953. this._renderDecorations = true;
  954. return true;
  955. }
  956. onFlushed() {
  957. this._lastRenderData = null;
  958. return true;
  959. }
  960. onLinesChanged(changeFromLineNumber, changeCount) {
  961. if (this._lastRenderData) {
  962. return this._lastRenderData.onLinesChanged(changeFromLineNumber, changeCount);
  963. }
  964. return false;
  965. }
  966. onLinesDeleted(deleteFromLineNumber, deleteToLineNumber) {
  967. var _a;
  968. (_a = this._lastRenderData) === null || _a === void 0 ? void 0 : _a.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber);
  969. return true;
  970. }
  971. onLinesInserted(insertFromLineNumber, insertToLineNumber) {
  972. var _a;
  973. (_a = this._lastRenderData) === null || _a === void 0 ? void 0 : _a.onLinesInserted(insertFromLineNumber, insertToLineNumber);
  974. return true;
  975. }
  976. onScrollChanged() {
  977. this._renderDecorations = true;
  978. return true;
  979. }
  980. onThemeChanged() {
  981. this._selectionColor = this._theme.getColor(minimapSelection);
  982. this._renderDecorations = true;
  983. return true;
  984. }
  985. onTokensChanged(ranges) {
  986. if (this._lastRenderData) {
  987. return this._lastRenderData.onTokensChanged(ranges);
  988. }
  989. return false;
  990. }
  991. onTokensColorsChanged() {
  992. this._lastRenderData = null;
  993. this._buffers = null;
  994. return true;
  995. }
  996. onZonesChanged() {
  997. this._lastRenderData = null;
  998. return true;
  999. }
  1000. // --- end event handlers
  1001. render(renderingCtx) {
  1002. const renderMinimap = this._model.options.renderMinimap;
  1003. if (renderMinimap === 0 /* RenderMinimap.None */) {
  1004. this._shadow.setClassName('minimap-shadow-hidden');
  1005. this._sliderHorizontal.setWidth(0);
  1006. this._sliderHorizontal.setHeight(0);
  1007. return;
  1008. }
  1009. if (renderingCtx.scrollLeft + renderingCtx.viewportWidth >= renderingCtx.scrollWidth) {
  1010. this._shadow.setClassName('minimap-shadow-hidden');
  1011. }
  1012. else {
  1013. this._shadow.setClassName('minimap-shadow-visible');
  1014. }
  1015. 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);
  1016. this._slider.setDisplay(layout.sliderNeeded ? 'block' : 'none');
  1017. this._slider.setTop(layout.sliderTop);
  1018. this._slider.setHeight(layout.sliderHeight);
  1019. // Compute horizontal slider coordinates
  1020. this._sliderHorizontal.setLeft(0);
  1021. this._sliderHorizontal.setWidth(this._model.options.minimapWidth);
  1022. this._sliderHorizontal.setTop(0);
  1023. this._sliderHorizontal.setHeight(layout.sliderHeight);
  1024. this.renderDecorations(layout);
  1025. this._lastRenderData = this.renderLines(layout);
  1026. }
  1027. renderDecorations(layout) {
  1028. if (this._renderDecorations) {
  1029. this._renderDecorations = false;
  1030. const selections = this._model.getSelections();
  1031. selections.sort(Range.compareRangesUsingStarts);
  1032. const decorations = this._model.getMinimapDecorationsInViewport(layout.startLineNumber, layout.endLineNumber);
  1033. decorations.sort((a, b) => (a.options.zIndex || 0) - (b.options.zIndex || 0));
  1034. const { canvasInnerWidth, canvasInnerHeight } = this._model.options;
  1035. const lineHeight = this._model.options.minimapLineHeight;
  1036. const characterWidth = this._model.options.minimapCharWidth;
  1037. const tabSize = this._model.getOptions().tabSize;
  1038. const canvasContext = this._decorationsCanvas.domNode.getContext('2d');
  1039. canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight);
  1040. // We first need to render line highlights and then render decorations on top of those.
  1041. // But we need to pick a single color for each line, and use that as a line highlight.
  1042. // This needs to be the color of the decoration with the highest `zIndex`, but priority
  1043. // is given to the selection.
  1044. const highlightedLines = new ContiguousLineMap(layout.startLineNumber, layout.endLineNumber, false);
  1045. this._renderSelectionLineHighlights(canvasContext, selections, highlightedLines, layout, lineHeight);
  1046. this._renderDecorationsLineHighlights(canvasContext, decorations, highlightedLines, layout, lineHeight);
  1047. const lineOffsetMap = new ContiguousLineMap(layout.startLineNumber, layout.endLineNumber, null);
  1048. this._renderSelectionsHighlights(canvasContext, selections, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth);
  1049. this._renderDecorationsHighlights(canvasContext, decorations, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth);
  1050. }
  1051. }
  1052. _renderSelectionLineHighlights(canvasContext, selections, highlightedLines, layout, lineHeight) {
  1053. if (!this._selectionColor || this._selectionColor.isTransparent()) {
  1054. return;
  1055. }
  1056. canvasContext.fillStyle = this._selectionColor.transparent(0.5).toString();
  1057. let y1 = 0;
  1058. let y2 = 0;
  1059. for (const selection of selections) {
  1060. const startLineNumber = Math.max(layout.startLineNumber, selection.startLineNumber);
  1061. const endLineNumber = Math.min(layout.endLineNumber, selection.endLineNumber);
  1062. if (startLineNumber > endLineNumber) {
  1063. // entirely outside minimap's viewport
  1064. continue;
  1065. }
  1066. for (let line = startLineNumber; line <= endLineNumber; line++) {
  1067. highlightedLines.set(line, true);
  1068. }
  1069. const yy1 = (startLineNumber - layout.startLineNumber) * lineHeight;
  1070. const yy2 = (endLineNumber - layout.startLineNumber) * lineHeight + lineHeight;
  1071. if (y2 >= yy1) {
  1072. // merge into previous
  1073. y2 = yy2;
  1074. }
  1075. else {
  1076. if (y2 > y1) {
  1077. // flush
  1078. canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y1, canvasContext.canvas.width, y2 - y1);
  1079. }
  1080. y1 = yy1;
  1081. y2 = yy2;
  1082. }
  1083. }
  1084. if (y2 > y1) {
  1085. // flush
  1086. canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y1, canvasContext.canvas.width, y2 - y1);
  1087. }
  1088. }
  1089. _renderDecorationsLineHighlights(canvasContext, decorations, highlightedLines, layout, lineHeight) {
  1090. const highlightColors = new Map();
  1091. // Loop backwards to hit first decorations with higher `zIndex`
  1092. for (let i = decorations.length - 1; i >= 0; i--) {
  1093. const decoration = decorations[i];
  1094. const minimapOptions = decoration.options.minimap;
  1095. if (!minimapOptions || minimapOptions.position !== MinimapPosition.Inline) {
  1096. continue;
  1097. }
  1098. const startLineNumber = Math.max(layout.startLineNumber, decoration.range.startLineNumber);
  1099. const endLineNumber = Math.min(layout.endLineNumber, decoration.range.endLineNumber);
  1100. if (startLineNumber > endLineNumber) {
  1101. // entirely outside minimap's viewport
  1102. continue;
  1103. }
  1104. const decorationColor = minimapOptions.getColor(this._theme.value);
  1105. if (!decorationColor || decorationColor.isTransparent()) {
  1106. continue;
  1107. }
  1108. let highlightColor = highlightColors.get(decorationColor.toString());
  1109. if (!highlightColor) {
  1110. highlightColor = decorationColor.transparent(0.5).toString();
  1111. highlightColors.set(decorationColor.toString(), highlightColor);
  1112. }
  1113. canvasContext.fillStyle = highlightColor;
  1114. for (let line = startLineNumber; line <= endLineNumber; line++) {
  1115. if (highlightedLines.has(line)) {
  1116. continue;
  1117. }
  1118. highlightedLines.set(line, true);
  1119. const y = (startLineNumber - layout.startLineNumber) * lineHeight;
  1120. canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y, canvasContext.canvas.width, lineHeight);
  1121. }
  1122. }
  1123. }
  1124. _renderSelectionsHighlights(canvasContext, selections, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth) {
  1125. if (!this._selectionColor || this._selectionColor.isTransparent()) {
  1126. return;
  1127. }
  1128. for (const selection of selections) {
  1129. const startLineNumber = Math.max(layout.startLineNumber, selection.startLineNumber);
  1130. const endLineNumber = Math.min(layout.endLineNumber, selection.endLineNumber);
  1131. if (startLineNumber > endLineNumber) {
  1132. // entirely outside minimap's viewport
  1133. continue;
  1134. }
  1135. for (let line = startLineNumber; line <= endLineNumber; line++) {
  1136. this.renderDecorationOnLine(canvasContext, lineOffsetMap, selection, this._selectionColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth, canvasInnerWidth);
  1137. }
  1138. }
  1139. }
  1140. _renderDecorationsHighlights(canvasContext, decorations, lineOffsetMap, layout, lineHeight, tabSize, characterWidth, canvasInnerWidth) {
  1141. // Loop forwards to hit first decorations with lower `zIndex`
  1142. for (const decoration of decorations) {
  1143. const minimapOptions = decoration.options.minimap;
  1144. if (!minimapOptions) {
  1145. continue;
  1146. }
  1147. const startLineNumber = Math.max(layout.startLineNumber, decoration.range.startLineNumber);
  1148. const endLineNumber = Math.min(layout.endLineNumber, decoration.range.endLineNumber);
  1149. if (startLineNumber > endLineNumber) {
  1150. // entirely outside minimap's viewport
  1151. continue;
  1152. }
  1153. const decorationColor = minimapOptions.getColor(this._theme.value);
  1154. if (!decorationColor || decorationColor.isTransparent()) {
  1155. continue;
  1156. }
  1157. for (let line = startLineNumber; line <= endLineNumber; line++) {
  1158. switch (minimapOptions.position) {
  1159. case MinimapPosition.Inline:
  1160. this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth, canvasInnerWidth);
  1161. continue;
  1162. case MinimapPosition.Gutter: {
  1163. const y = (line - layout.startLineNumber) * lineHeight;
  1164. const x = 2;
  1165. this.renderDecoration(canvasContext, decorationColor, x, y, GUTTER_DECORATION_WIDTH, lineHeight);
  1166. continue;
  1167. }
  1168. }
  1169. }
  1170. }
  1171. }
  1172. renderDecorationOnLine(canvasContext, lineOffsetMap, decorationRange, decorationColor, layout, lineNumber, height, lineHeight, tabSize, charWidth, canvasInnerWidth) {
  1173. const y = (lineNumber - layout.startLineNumber) * lineHeight;
  1174. // Skip rendering the line if it's vertically outside our viewport
  1175. if (y + height < 0 || y > this._model.options.canvasInnerHeight) {
  1176. return;
  1177. }
  1178. const { startLineNumber, endLineNumber } = decorationRange;
  1179. const startColumn = (startLineNumber === lineNumber ? decorationRange.startColumn : 1);
  1180. const endColumn = (endLineNumber === lineNumber ? decorationRange.endColumn : this._model.getLineMaxColumn(lineNumber));
  1181. const x1 = this.getXOffsetForPosition(lineOffsetMap, lineNumber, startColumn, tabSize, charWidth, canvasInnerWidth);
  1182. const x2 = this.getXOffsetForPosition(lineOffsetMap, lineNumber, endColumn, tabSize, charWidth, canvasInnerWidth);
  1183. this.renderDecoration(canvasContext, decorationColor, x1, y, x2 - x1, height);
  1184. }
  1185. getXOffsetForPosition(lineOffsetMap, lineNumber, column, tabSize, charWidth, canvasInnerWidth) {
  1186. if (column === 1) {
  1187. return MINIMAP_GUTTER_WIDTH;
  1188. }
  1189. const minimumXOffset = (column - 1) * charWidth;
  1190. if (minimumXOffset >= canvasInnerWidth) {
  1191. // there is no need to look at actual characters,
  1192. // as this column is certainly after the minimap width
  1193. return canvasInnerWidth;
  1194. }
  1195. // Cache line offset data so that it is only read once per line
  1196. let lineIndexToXOffset = lineOffsetMap.get(lineNumber);
  1197. if (!lineIndexToXOffset) {
  1198. const lineData = this._model.getLineContent(lineNumber);
  1199. lineIndexToXOffset = [MINIMAP_GUTTER_WIDTH];
  1200. let prevx = MINIMAP_GUTTER_WIDTH;
  1201. for (let i = 1; i < lineData.length + 1; i++) {
  1202. const charCode = lineData.charCodeAt(i - 1);
  1203. const dx = charCode === 9 /* CharCode.Tab */
  1204. ? tabSize * charWidth
  1205. : strings.isFullWidthCharacter(charCode)
  1206. ? 2 * charWidth
  1207. : charWidth;
  1208. const x = prevx + dx;
  1209. if (x >= canvasInnerWidth) {
  1210. // no need to keep on going, as we've hit the canvas width
  1211. lineIndexToXOffset[i] = canvasInnerWidth;
  1212. break;
  1213. }
  1214. lineIndexToXOffset[i] = x;
  1215. prevx = x;
  1216. }
  1217. lineOffsetMap.set(lineNumber, lineIndexToXOffset);
  1218. }
  1219. if (column - 1 < lineIndexToXOffset.length) {
  1220. return lineIndexToXOffset[column - 1];
  1221. }
  1222. // goes over the canvas width
  1223. return canvasInnerWidth;
  1224. }
  1225. renderDecoration(canvasContext, decorationColor, x, y, width, height) {
  1226. canvasContext.fillStyle = decorationColor && decorationColor.toString() || '';
  1227. canvasContext.fillRect(x, y, width, height);
  1228. }
  1229. renderLines(layout) {
  1230. const startLineNumber = layout.startLineNumber;
  1231. const endLineNumber = layout.endLineNumber;
  1232. const minimapLineHeight = this._model.options.minimapLineHeight;
  1233. // Check if nothing changed w.r.t. lines from last frame
  1234. if (this._lastRenderData && this._lastRenderData.linesEquals(layout)) {
  1235. const _lastData = this._lastRenderData._get();
  1236. // Nice!! Nothing changed from last frame
  1237. return new RenderData(layout, _lastData.imageData, _lastData.lines);
  1238. }
  1239. // Oh well!! We need to repaint some lines...
  1240. const imageData = this._getBuffer();
  1241. if (!imageData) {
  1242. // 0 width or 0 height canvas, nothing to do
  1243. return null;
  1244. }
  1245. // Render untouched lines by using last rendered data.
  1246. const [_dirtyY1, _dirtyY2, needed] = InnerMinimap._renderUntouchedLines(imageData, startLineNumber, endLineNumber, minimapLineHeight, this._lastRenderData);
  1247. // Fetch rendering info from view model for rest of lines that need rendering.
  1248. const lineInfo = this._model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed);
  1249. const tabSize = this._model.getOptions().tabSize;
  1250. const defaultBackground = this._model.options.defaultBackgroundColor;
  1251. const background = this._model.options.backgroundColor;
  1252. const foregroundAlpha = this._model.options.foregroundAlpha;
  1253. const tokensColorTracker = this._model.tokensColorTracker;
  1254. const useLighterFont = tokensColorTracker.backgroundIsLight();
  1255. const renderMinimap = this._model.options.renderMinimap;
  1256. const charRenderer = this._model.options.charRenderer();
  1257. const fontScale = this._model.options.fontScale;
  1258. const minimapCharWidth = this._model.options.minimapCharWidth;
  1259. const baseCharHeight = (renderMinimap === 1 /* RenderMinimap.Text */ ? 2 /* Constants.BASE_CHAR_HEIGHT */ : 2 /* Constants.BASE_CHAR_HEIGHT */ + 1);
  1260. const renderMinimapLineHeight = baseCharHeight * fontScale;
  1261. const innerLinePadding = (minimapLineHeight > renderMinimapLineHeight ? Math.floor((minimapLineHeight - renderMinimapLineHeight) / 2) : 0);
  1262. // Render the rest of lines
  1263. const backgroundA = background.a / 255;
  1264. 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);
  1265. let dy = 0;
  1266. const renderedLines = [];
  1267. for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) {
  1268. if (needed[lineIndex]) {
  1269. InnerMinimap._renderLine(imageData, renderBackground, background.a, useLighterFont, renderMinimap, minimapCharWidth, tokensColorTracker, foregroundAlpha, charRenderer, dy, innerLinePadding, tabSize, lineInfo[lineIndex], fontScale, minimapLineHeight);
  1270. }
  1271. renderedLines[lineIndex] = new MinimapLine(dy);
  1272. dy += minimapLineHeight;
  1273. }
  1274. const dirtyY1 = (_dirtyY1 === -1 ? 0 : _dirtyY1);
  1275. const dirtyY2 = (_dirtyY2 === -1 ? imageData.height : _dirtyY2);
  1276. const dirtyHeight = dirtyY2 - dirtyY1;
  1277. // Finally, paint to the canvas
  1278. const ctx = this._canvas.domNode.getContext('2d');
  1279. ctx.putImageData(imageData, 0, 0, 0, dirtyY1, imageData.width, dirtyHeight);
  1280. // Save rendered data for reuse on next frame if possible
  1281. return new RenderData(layout, imageData, renderedLines);
  1282. }
  1283. static _renderUntouchedLines(target, startLineNumber, endLineNumber, minimapLineHeight, lastRenderData) {
  1284. const needed = [];
  1285. if (!lastRenderData) {
  1286. for (let i = 0, len = endLineNumber - startLineNumber + 1; i < len; i++) {
  1287. needed[i] = true;
  1288. }
  1289. return [-1, -1, needed];
  1290. }
  1291. const _lastData = lastRenderData._get();
  1292. const lastTargetData = _lastData.imageData.data;
  1293. const lastStartLineNumber = _lastData.rendLineNumberStart;
  1294. const lastLines = _lastData.lines;
  1295. const lastLinesLength = lastLines.length;
  1296. const WIDTH = target.width;
  1297. const targetData = target.data;
  1298. const maxDestPixel = (endLineNumber - startLineNumber + 1) * minimapLineHeight * WIDTH * 4;
  1299. let dirtyPixel1 = -1; // the pixel offset up to which all the data is equal to the prev frame
  1300. let dirtyPixel2 = -1; // the pixel offset after which all the data is equal to the prev frame
  1301. let copySourceStart = -1;
  1302. let copySourceEnd = -1;
  1303. let copyDestStart = -1;
  1304. let copyDestEnd = -1;
  1305. let dest_dy = 0;
  1306. for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
  1307. const lineIndex = lineNumber - startLineNumber;
  1308. const lastLineIndex = lineNumber - lastStartLineNumber;
  1309. const source_dy = (lastLineIndex >= 0 && lastLineIndex < lastLinesLength ? lastLines[lastLineIndex].dy : -1);
  1310. if (source_dy === -1) {
  1311. needed[lineIndex] = true;
  1312. dest_dy += minimapLineHeight;
  1313. continue;
  1314. }
  1315. const sourceStart = source_dy * WIDTH * 4;
  1316. const sourceEnd = (source_dy + minimapLineHeight) * WIDTH * 4;
  1317. const destStart = dest_dy * WIDTH * 4;
  1318. const destEnd = (dest_dy + minimapLineHeight) * WIDTH * 4;
  1319. if (copySourceEnd === sourceStart && copyDestEnd === destStart) {
  1320. // contiguous zone => extend copy request
  1321. copySourceEnd = sourceEnd;
  1322. copyDestEnd = destEnd;
  1323. }
  1324. else {
  1325. if (copySourceStart !== -1) {
  1326. // flush existing copy request
  1327. targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
  1328. if (dirtyPixel1 === -1 && copySourceStart === 0 && copySourceStart === copyDestStart) {
  1329. dirtyPixel1 = copySourceEnd;
  1330. }
  1331. if (dirtyPixel2 === -1 && copySourceEnd === maxDestPixel && copySourceStart === copyDestStart) {
  1332. dirtyPixel2 = copySourceStart;
  1333. }
  1334. }
  1335. copySourceStart = sourceStart;
  1336. copySourceEnd = sourceEnd;
  1337. copyDestStart = destStart;
  1338. copyDestEnd = destEnd;
  1339. }
  1340. needed[lineIndex] = false;
  1341. dest_dy += minimapLineHeight;
  1342. }
  1343. if (copySourceStart !== -1) {
  1344. // flush existing copy request
  1345. targetData.set(lastTargetData.subarray(copySourceStart, copySourceEnd), copyDestStart);
  1346. if (dirtyPixel1 === -1 && copySourceStart === 0 && copySourceStart === copyDestStart) {
  1347. dirtyPixel1 = copySourceEnd;
  1348. }
  1349. if (dirtyPixel2 === -1 && copySourceEnd === maxDestPixel && copySourceStart === copyDestStart) {
  1350. dirtyPixel2 = copySourceStart;
  1351. }
  1352. }
  1353. const dirtyY1 = (dirtyPixel1 === -1 ? -1 : dirtyPixel1 / (WIDTH * 4));
  1354. const dirtyY2 = (dirtyPixel2 === -1 ? -1 : dirtyPixel2 / (WIDTH * 4));
  1355. return [dirtyY1, dirtyY2, needed];
  1356. }
  1357. static _renderLine(target, backgroundColor, backgroundAlpha, useLighterFont, renderMinimap, charWidth, colorTracker, foregroundAlpha, minimapCharRenderer, dy, innerLinePadding, tabSize, lineData, fontScale, minimapLineHeight) {
  1358. const content = lineData.content;
  1359. const tokens = lineData.tokens;
  1360. const maxDx = target.width - charWidth;
  1361. const force1pxHeight = (minimapLineHeight === 1);
  1362. let dx = MINIMAP_GUTTER_WIDTH;
  1363. let charIndex = 0;
  1364. let tabsCharDelta = 0;
  1365. for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
  1366. const tokenEndIndex = tokens.getEndOffset(tokenIndex);
  1367. const tokenColorId = tokens.getForeground(tokenIndex);
  1368. const tokenColor = colorTracker.getColor(tokenColorId);
  1369. for (; charIndex < tokenEndIndex; charIndex++) {
  1370. if (dx > maxDx) {
  1371. // hit edge of minimap
  1372. return;
  1373. }
  1374. const charCode = content.charCodeAt(charIndex);
  1375. if (charCode === 9 /* CharCode.Tab */) {
  1376. const insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
  1377. tabsCharDelta += insertSpacesCount - 1;
  1378. // No need to render anything since tab is invisible
  1379. dx += insertSpacesCount * charWidth;
  1380. }
  1381. else if (charCode === 32 /* CharCode.Space */) {
  1382. // No need to render anything since space is invisible
  1383. dx += charWidth;
  1384. }
  1385. else {
  1386. // Render twice for a full width character
  1387. const count = strings.isFullWidthCharacter(charCode) ? 2 : 1;
  1388. for (let i = 0; i < count; i++) {
  1389. if (renderMinimap === 2 /* RenderMinimap.Blocks */) {
  1390. minimapCharRenderer.blockRenderChar(target, dx, dy + innerLinePadding, tokenColor, foregroundAlpha, backgroundColor, backgroundAlpha, force1pxHeight);
  1391. }
  1392. else { // RenderMinimap.Text
  1393. minimapCharRenderer.renderChar(target, dx, dy + innerLinePadding, charCode, tokenColor, foregroundAlpha, backgroundColor, backgroundAlpha, fontScale, useLighterFont, force1pxHeight);
  1394. }
  1395. dx += charWidth;
  1396. if (dx > maxDx) {
  1397. // hit edge of minimap
  1398. return;
  1399. }
  1400. }
  1401. }
  1402. }
  1403. }
  1404. }
  1405. }
  1406. class ContiguousLineMap {
  1407. constructor(startLineNumber, endLineNumber, defaultValue) {
  1408. this._startLineNumber = startLineNumber;
  1409. this._endLineNumber = endLineNumber;
  1410. this._defaultValue = defaultValue;
  1411. this._values = [];
  1412. for (let i = 0, count = this._endLineNumber - this._startLineNumber + 1; i < count; i++) {
  1413. this._values[i] = defaultValue;
  1414. }
  1415. }
  1416. has(lineNumber) {
  1417. return (this.get(lineNumber) !== this._defaultValue);
  1418. }
  1419. set(lineNumber, value) {
  1420. if (lineNumber < this._startLineNumber || lineNumber > this._endLineNumber) {
  1421. return;
  1422. }
  1423. this._values[lineNumber - this._startLineNumber] = value;
  1424. }
  1425. get(lineNumber) {
  1426. if (lineNumber < this._startLineNumber || lineNumber > this._endLineNumber) {
  1427. return this._defaultValue;
  1428. }
  1429. return this._values[lineNumber - this._startLineNumber];
  1430. }
  1431. }
  1432. registerThemingParticipant((theme, collector) => {
  1433. const sliderBackground = theme.getColor(minimapSliderBackground);
  1434. if (sliderBackground) {
  1435. collector.addRule(`.monaco-editor .minimap-slider .minimap-slider-horizontal { background: ${sliderBackground}; }`);
  1436. }
  1437. const sliderHoverBackground = theme.getColor(minimapSliderHoverBackground);
  1438. if (sliderHoverBackground) {
  1439. collector.addRule(`.monaco-editor .minimap-slider:hover .minimap-slider-horizontal { background: ${sliderHoverBackground}; }`);
  1440. }
  1441. const sliderActiveBackground = theme.getColor(minimapSliderActiveBackground);
  1442. if (sliderActiveBackground) {
  1443. collector.addRule(`.monaco-editor .minimap-slider.active .minimap-slider-horizontal { background: ${sliderActiveBackground}; }`);
  1444. }
  1445. const shadow = theme.getColor(scrollbarShadow);
  1446. if (shadow) {
  1447. collector.addRule(`.monaco-editor .minimap-shadow-visible { box-shadow: ${shadow} -6px 0 6px -6px inset; }`);
  1448. }
  1449. });