UNPKG

monaco-editor

Version:
414 lines (413 loc) • 19 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { createFastDomNode } from '../../../../base/browser/fastDomNode.js'; import { Color } from '../../../../base/common/color.js'; import { ViewPart } from '../../view/viewPart.js'; import { Position } from '../../../common/core/position.js'; import { TokenizationRegistry } from '../../../common/languages.js'; import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground, editorMultiCursorSecondaryForeground, editorMultiCursorPrimaryForeground } from '../../../common/core/editorColorRegistry.js'; import { OverviewRulerDecorationsGroup } from '../../../common/viewModel.js'; import { equals } from '../../../../base/common/arrays.js'; class Settings { constructor(config, theme) { const options = config.options; this.lineHeight = options.get(67 /* EditorOption.lineHeight */); this.pixelRatio = options.get(144 /* EditorOption.pixelRatio */); this.overviewRulerLanes = options.get(83 /* EditorOption.overviewRulerLanes */); this.renderBorder = options.get(82 /* EditorOption.overviewRulerBorder */); const borderColor = theme.getColor(editorOverviewRulerBorder); this.borderColor = borderColor ? borderColor.toString() : null; this.hideCursor = options.get(59 /* EditorOption.hideCursorInOverviewRuler */); const cursorColorSingle = theme.getColor(editorCursorForeground); this.cursorColorSingle = cursorColorSingle ? cursorColorSingle.transparent(0.7).toString() : null; const cursorColorPrimary = theme.getColor(editorMultiCursorPrimaryForeground); this.cursorColorPrimary = cursorColorPrimary ? cursorColorPrimary.transparent(0.7).toString() : null; const cursorColorSecondary = theme.getColor(editorMultiCursorSecondaryForeground); this.cursorColorSecondary = cursorColorSecondary ? cursorColorSecondary.transparent(0.7).toString() : null; this.themeType = theme.type; const minimapOpts = options.get(73 /* EditorOption.minimap */); const minimapEnabled = minimapOpts.enabled; const minimapSide = minimapOpts.side; const themeColor = theme.getColor(editorOverviewRulerBackground); const defaultBackground = TokenizationRegistry.getDefaultBackground(); if (themeColor) { this.backgroundColor = themeColor; } else if (minimapEnabled && minimapSide === 'right') { this.backgroundColor = defaultBackground; } else { this.backgroundColor = null; } const layoutInfo = options.get(146 /* EditorOption.layoutInfo */); const position = layoutInfo.overviewRuler; this.top = position.top; this.right = position.right; this.domWidth = position.width; this.domHeight = position.height; if (this.overviewRulerLanes === 0) { // overview ruler is off this.canvasWidth = 0; this.canvasHeight = 0; } else { this.canvasWidth = (this.domWidth * this.pixelRatio) | 0; this.canvasHeight = (this.domHeight * this.pixelRatio) | 0; } const [x, w] = this._initLanes(1, this.canvasWidth, this.overviewRulerLanes); this.x = x; this.w = w; } _initLanes(canvasLeftOffset, canvasWidth, laneCount) { const remainingWidth = canvasWidth - canvasLeftOffset; if (laneCount >= 3) { const leftWidth = Math.floor(remainingWidth / 3); const rightWidth = Math.floor(remainingWidth / 3); const centerWidth = remainingWidth - leftWidth - rightWidth; const leftOffset = canvasLeftOffset; const centerOffset = leftOffset + leftWidth; const rightOffset = leftOffset + leftWidth + centerWidth; return [ [ 0, leftOffset, // Left centerOffset, // Center leftOffset, // Left | Center rightOffset, // Right leftOffset, // Left | Right centerOffset, // Center | Right leftOffset, // Left | Center | Right ], [ 0, leftWidth, // Left centerWidth, // Center leftWidth + centerWidth, // Left | Center rightWidth, // Right leftWidth + centerWidth + rightWidth, // Left | Right centerWidth + rightWidth, // Center | Right leftWidth + centerWidth + rightWidth, // Left | Center | Right ] ]; } else if (laneCount === 2) { const leftWidth = Math.floor(remainingWidth / 2); const rightWidth = remainingWidth - leftWidth; const leftOffset = canvasLeftOffset; const rightOffset = leftOffset + leftWidth; return [ [ 0, leftOffset, // Left leftOffset, // Center leftOffset, // Left | Center rightOffset, // Right leftOffset, // Left | Right leftOffset, // Center | Right leftOffset, // Left | Center | Right ], [ 0, leftWidth, // Left leftWidth, // Center leftWidth, // Left | Center rightWidth, // Right leftWidth + rightWidth, // Left | Right leftWidth + rightWidth, // Center | Right leftWidth + rightWidth, // Left | Center | Right ] ]; } else { const offset = canvasLeftOffset; const width = remainingWidth; return [ [ 0, offset, // Left offset, // Center offset, // Left | Center offset, // Right offset, // Left | Right offset, // Center | Right offset, // Left | Center | Right ], [ 0, width, // Left width, // Center width, // Left | Center width, // Right width, // Left | Right width, // Center | Right width, // Left | Center | Right ] ]; } } equals(other) { return (this.lineHeight === other.lineHeight && this.pixelRatio === other.pixelRatio && this.overviewRulerLanes === other.overviewRulerLanes && this.renderBorder === other.renderBorder && this.borderColor === other.borderColor && this.hideCursor === other.hideCursor && this.cursorColorSingle === other.cursorColorSingle && this.cursorColorPrimary === other.cursorColorPrimary && this.cursorColorSecondary === other.cursorColorSecondary && this.themeType === other.themeType && Color.equals(this.backgroundColor, other.backgroundColor) && this.top === other.top && this.right === other.right && this.domWidth === other.domWidth && this.domHeight === other.domHeight && this.canvasWidth === other.canvasWidth && this.canvasHeight === other.canvasHeight); } } export class DecorationsOverviewRuler extends ViewPart { constructor(context) { super(context); this._actualShouldRender = 0 /* ShouldRenderValue.NotNeeded */; this._renderedDecorations = []; this._renderedCursorPositions = []; this._domNode = createFastDomNode(document.createElement('canvas')); this._domNode.setClassName('decorationsOverviewRuler'); this._domNode.setPosition('absolute'); this._domNode.setLayerHinting(true); this._domNode.setContain('strict'); this._domNode.setAttribute('aria-hidden', 'true'); this._updateSettings(false); this._tokensColorTrackerListener = TokenizationRegistry.onDidChange((e) => { if (e.changedColorMap) { this._updateSettings(true); } }); this._cursorPositions = [{ position: new Position(1, 1), color: this._settings.cursorColorSingle }]; } dispose() { super.dispose(); this._tokensColorTrackerListener.dispose(); } _updateSettings(renderNow) { const newSettings = new Settings(this._context.configuration, this._context.theme); if (this._settings && this._settings.equals(newSettings)) { // nothing to do return false; } this._settings = newSettings; this._domNode.setTop(this._settings.top); this._domNode.setRight(this._settings.right); this._domNode.setWidth(this._settings.domWidth); this._domNode.setHeight(this._settings.domHeight); this._domNode.domNode.width = this._settings.canvasWidth; this._domNode.domNode.height = this._settings.canvasHeight; if (renderNow) { this._render(); } return true; } // ---- begin view event handlers _markRenderingIsNeeded() { this._actualShouldRender = 2 /* ShouldRenderValue.Needed */; return true; } _markRenderingIsMaybeNeeded() { this._actualShouldRender = 1 /* ShouldRenderValue.Maybe */; return true; } onConfigurationChanged(e) { return this._updateSettings(false) ? this._markRenderingIsNeeded() : false; } onCursorStateChanged(e) { this._cursorPositions = []; for (let i = 0, len = e.selections.length; i < len; i++) { let color = this._settings.cursorColorSingle; if (len > 1) { color = i === 0 ? this._settings.cursorColorPrimary : this._settings.cursorColorSecondary; } this._cursorPositions.push({ position: e.selections[i].getPosition(), color }); } this._cursorPositions.sort((a, b) => Position.compare(a.position, b.position)); return this._markRenderingIsMaybeNeeded(); } onDecorationsChanged(e) { if (e.affectsOverviewRuler) { return this._markRenderingIsMaybeNeeded(); } return false; } onFlushed(e) { return this._markRenderingIsNeeded(); } onScrollChanged(e) { return e.scrollHeightChanged ? this._markRenderingIsNeeded() : false; } onZonesChanged(e) { return this._markRenderingIsNeeded(); } onThemeChanged(e) { return this._updateSettings(false) ? this._markRenderingIsNeeded() : false; } // ---- end view event handlers getDomNode() { return this._domNode.domNode; } prepareRender(ctx) { // Nothing to read } render(editorCtx) { this._render(); this._actualShouldRender = 0 /* ShouldRenderValue.NotNeeded */; } _render() { const backgroundColor = this._settings.backgroundColor; if (this._settings.overviewRulerLanes === 0) { // overview ruler is off this._domNode.setBackgroundColor(backgroundColor ? Color.Format.CSS.formatHexA(backgroundColor) : ''); this._domNode.setDisplay('none'); return; } const decorations = this._context.viewModel.getAllOverviewRulerDecorations(this._context.theme); decorations.sort(OverviewRulerDecorationsGroup.compareByRenderingProps); if (this._actualShouldRender === 1 /* ShouldRenderValue.Maybe */ && !OverviewRulerDecorationsGroup.equalsArr(this._renderedDecorations, decorations)) { this._actualShouldRender = 2 /* ShouldRenderValue.Needed */; } if (this._actualShouldRender === 1 /* ShouldRenderValue.Maybe */ && !equals(this._renderedCursorPositions, this._cursorPositions, (a, b) => a.position.lineNumber === b.position.lineNumber && a.color === b.color)) { this._actualShouldRender = 2 /* ShouldRenderValue.Needed */; } if (this._actualShouldRender === 1 /* ShouldRenderValue.Maybe */) { // both decorations and cursor positions are unchanged, nothing to do return; } this._renderedDecorations = decorations; this._renderedCursorPositions = this._cursorPositions; this._domNode.setDisplay('block'); const canvasWidth = this._settings.canvasWidth; const canvasHeight = this._settings.canvasHeight; const lineHeight = this._settings.lineHeight; const viewLayout = this._context.viewLayout; const outerHeight = this._context.viewLayout.getScrollHeight(); const heightRatio = canvasHeight / outerHeight; const minDecorationHeight = (6 /* Constants.MIN_DECORATION_HEIGHT */ * this._settings.pixelRatio) | 0; const halfMinDecorationHeight = (minDecorationHeight / 2) | 0; const canvasCtx = this._domNode.domNode.getContext('2d'); if (backgroundColor) { if (backgroundColor.isOpaque()) { // We have a background color which is opaque, we can just paint the entire surface with it canvasCtx.fillStyle = Color.Format.CSS.formatHexA(backgroundColor); canvasCtx.fillRect(0, 0, canvasWidth, canvasHeight); } else { // We have a background color which is transparent, we need to first clear the surface and // then fill it canvasCtx.clearRect(0, 0, canvasWidth, canvasHeight); canvasCtx.fillStyle = Color.Format.CSS.formatHexA(backgroundColor); canvasCtx.fillRect(0, 0, canvasWidth, canvasHeight); } } else { // We don't have a background color canvasCtx.clearRect(0, 0, canvasWidth, canvasHeight); } const x = this._settings.x; const w = this._settings.w; for (const decorationGroup of decorations) { const color = decorationGroup.color; const decorationGroupData = decorationGroup.data; canvasCtx.fillStyle = color; let prevLane = 0; let prevY1 = 0; let prevY2 = 0; for (let i = 0, len = decorationGroupData.length / 3; i < len; i++) { const lane = decorationGroupData[3 * i]; const startLineNumber = decorationGroupData[3 * i + 1]; const endLineNumber = decorationGroupData[3 * i + 2]; let y1 = (viewLayout.getVerticalOffsetForLineNumber(startLineNumber) * heightRatio) | 0; let y2 = ((viewLayout.getVerticalOffsetForLineNumber(endLineNumber) + lineHeight) * heightRatio) | 0; const height = y2 - y1; if (height < minDecorationHeight) { let yCenter = ((y1 + y2) / 2) | 0; if (yCenter < halfMinDecorationHeight) { yCenter = halfMinDecorationHeight; } else if (yCenter + halfMinDecorationHeight > canvasHeight) { yCenter = canvasHeight - halfMinDecorationHeight; } y1 = yCenter - halfMinDecorationHeight; y2 = yCenter + halfMinDecorationHeight; } if (y1 > prevY2 + 1 || lane !== prevLane) { // flush prev if (i !== 0) { canvasCtx.fillRect(x[prevLane], prevY1, w[prevLane], prevY2 - prevY1); } prevLane = lane; prevY1 = y1; prevY2 = y2; } else { // merge into prev if (y2 > prevY2) { prevY2 = y2; } } } canvasCtx.fillRect(x[prevLane], prevY1, w[prevLane], prevY2 - prevY1); } // Draw cursors if (!this._settings.hideCursor) { const cursorHeight = (2 * this._settings.pixelRatio) | 0; const halfCursorHeight = (cursorHeight / 2) | 0; const cursorX = this._settings.x[7 /* OverviewRulerLane.Full */]; const cursorW = this._settings.w[7 /* OverviewRulerLane.Full */]; let prevY1 = -100; let prevY2 = -100; let prevColor = null; for (let i = 0, len = this._cursorPositions.length; i < len; i++) { const color = this._cursorPositions[i].color; if (!color) { continue; } const cursor = this._cursorPositions[i].position; let yCenter = (viewLayout.getVerticalOffsetForLineNumber(cursor.lineNumber) * heightRatio) | 0; if (yCenter < halfCursorHeight) { yCenter = halfCursorHeight; } else if (yCenter + halfCursorHeight > canvasHeight) { yCenter = canvasHeight - halfCursorHeight; } const y1 = yCenter - halfCursorHeight; const y2 = y1 + cursorHeight; if (y1 > prevY2 + 1 || color !== prevColor) { // flush prev if (i !== 0 && prevColor) { canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1); } prevY1 = y1; prevY2 = y2; } else { // merge into prev if (y2 > prevY2) { prevY2 = y2; } } prevColor = color; canvasCtx.fillStyle = color; } if (prevColor) { canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1); } } if (this._settings.renderBorder && this._settings.borderColor && this._settings.overviewRulerLanes > 0) { canvasCtx.beginPath(); canvasCtx.lineWidth = 1; canvasCtx.strokeStyle = this._settings.borderColor; canvasCtx.moveTo(0, 0); canvasCtx.lineTo(0, canvasHeight); canvasCtx.moveTo(1, 0); canvasCtx.lineTo(canvasWidth, 0); canvasCtx.stroke(); } } }