monaco-editor-core
Version:
A browser based code editor
248 lines (247 loc) • 15.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './indentGuides.css';
import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
import { editorBracketHighlightingForeground1, editorBracketHighlightingForeground2, editorBracketHighlightingForeground3, editorBracketHighlightingForeground4, editorBracketHighlightingForeground5, editorBracketHighlightingForeground6, editorBracketPairGuideActiveBackground1, editorBracketPairGuideActiveBackground2, editorBracketPairGuideActiveBackground3, editorBracketPairGuideActiveBackground4, editorBracketPairGuideActiveBackground5, editorBracketPairGuideActiveBackground6, editorBracketPairGuideBackground1, editorBracketPairGuideBackground2, editorBracketPairGuideBackground3, editorBracketPairGuideBackground4, editorBracketPairGuideBackground5, editorBracketPairGuideBackground6, editorIndentGuide1, editorIndentGuide2, editorIndentGuide3, editorIndentGuide4, editorIndentGuide5, editorIndentGuide6, editorActiveIndentGuide1, editorActiveIndentGuide2, editorActiveIndentGuide3, editorActiveIndentGuide4, editorActiveIndentGuide5, editorActiveIndentGuide6 } from '../../../common/core/editorColorRegistry.js';
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { Position } from '../../../common/core/position.js';
import { ArrayQueue } from '../../../../base/common/arrays.js';
import { isDefined } from '../../../../base/common/types.js';
import { BracketPairGuidesClassNames } from '../../../common/model/guidesTextModelPart.js';
import { IndentGuide, HorizontalGuidesState } from '../../../common/textModelGuides.js';
export class IndentGuidesOverlay extends DynamicViewOverlay {
constructor(context) {
super();
this._context = context;
this._primaryPosition = null;
const options = this._context.configuration.options;
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._spaceWidth = fontInfo.spaceWidth;
this._maxIndentLeft = wrappingInfo.wrappingColumn === -1 ? -1 : (wrappingInfo.wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth);
this._bracketPairGuideOptions = options.get(16 /* EditorOption.guides */);
this._renderResult = null;
this._context.addEventHandler(this);
}
dispose() {
this._context.removeEventHandler(this);
this._renderResult = null;
super.dispose();
}
// --- begin event handlers
onConfigurationChanged(e) {
const options = this._context.configuration.options;
const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */);
const fontInfo = options.get(50 /* EditorOption.fontInfo */);
this._spaceWidth = fontInfo.spaceWidth;
this._maxIndentLeft = wrappingInfo.wrappingColumn === -1 ? -1 : (wrappingInfo.wrappingColumn * fontInfo.typicalHalfwidthCharacterWidth);
this._bracketPairGuideOptions = options.get(16 /* EditorOption.guides */);
return true;
}
onCursorStateChanged(e) {
const selection = e.selections[0];
const newPosition = selection.getPosition();
if (!this._primaryPosition?.equals(newPosition)) {
this._primaryPosition = newPosition;
return true;
}
return false;
}
onDecorationsChanged(e) {
// true for inline decorations
return true;
}
onFlushed(e) {
return true;
}
onLinesChanged(e) {
return true;
}
onLinesDeleted(e) {
return true;
}
onLinesInserted(e) {
return true;
}
onScrollChanged(e) {
return e.scrollTopChanged; // || e.scrollWidthChanged;
}
onZonesChanged(e) {
return true;
}
onLanguageConfigurationChanged(e) {
return true;
}
// --- end event handlers
prepareRender(ctx) {
if (!this._bracketPairGuideOptions.indentation && this._bracketPairGuideOptions.bracketPairs === false) {
this._renderResult = null;
return;
}
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const scrollWidth = ctx.scrollWidth;
const activeCursorPosition = this._primaryPosition;
const indents = this.getGuidesByLine(visibleStartLineNumber, Math.min(visibleEndLineNumber + 1, this._context.viewModel.getLineCount()), activeCursorPosition);
const output = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
const indent = indents[lineIndex];
let result = '';
const leftOffset = ctx.visibleRangeForPosition(new Position(lineNumber, 1))?.left ?? 0;
for (const guide of indent) {
const left = guide.column === -1
? leftOffset + (guide.visibleColumn - 1) * this._spaceWidth
: ctx.visibleRangeForPosition(new Position(lineNumber, guide.column)).left;
if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) {
break;
}
const className = guide.horizontalLine ? (guide.horizontalLine.top ? 'horizontal-top' : 'horizontal-bottom') : 'vertical';
const width = guide.horizontalLine
? (ctx.visibleRangeForPosition(new Position(lineNumber, guide.horizontalLine.endColumn))?.left ?? (left + this._spaceWidth)) - left
: this._spaceWidth;
result += `<div class="core-guide ${guide.className} ${className}" style="left:${left}px;width:${width}px"></div>`;
}
output[lineIndex] = result;
}
this._renderResult = output;
}
getGuidesByLine(visibleStartLineNumber, visibleEndLineNumber, activeCursorPosition) {
const bracketGuides = this._bracketPairGuideOptions.bracketPairs !== false
? this._context.viewModel.getBracketGuidesInRangeByLine(visibleStartLineNumber, visibleEndLineNumber, activeCursorPosition, {
highlightActive: this._bracketPairGuideOptions.highlightActiveBracketPair,
horizontalGuides: this._bracketPairGuideOptions.bracketPairsHorizontal === true
? HorizontalGuidesState.Enabled
: this._bracketPairGuideOptions.bracketPairsHorizontal === 'active'
? HorizontalGuidesState.EnabledForActive
: HorizontalGuidesState.Disabled,
includeInactive: this._bracketPairGuideOptions.bracketPairs === true,
})
: null;
const indentGuides = this._bracketPairGuideOptions.indentation
? this._context.viewModel.getLinesIndentGuides(visibleStartLineNumber, visibleEndLineNumber)
: null;
let activeIndentStartLineNumber = 0;
let activeIndentEndLineNumber = 0;
let activeIndentLevel = 0;
if (this._bracketPairGuideOptions.highlightActiveIndentation !== false && activeCursorPosition) {
const activeIndentInfo = this._context.viewModel.getActiveIndentGuide(activeCursorPosition.lineNumber, visibleStartLineNumber, visibleEndLineNumber);
activeIndentStartLineNumber = activeIndentInfo.startLineNumber;
activeIndentEndLineNumber = activeIndentInfo.endLineNumber;
activeIndentLevel = activeIndentInfo.indent;
}
const { indentSize } = this._context.viewModel.model.getOptions();
const result = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineGuides = new Array();
result.push(lineGuides);
const bracketGuidesInLine = bracketGuides ? bracketGuides[lineNumber - visibleStartLineNumber] : [];
const bracketGuidesInLineQueue = new ArrayQueue(bracketGuidesInLine);
const indentGuidesInLine = indentGuides ? indentGuides[lineNumber - visibleStartLineNumber] : 0;
for (let indentLvl = 1; indentLvl <= indentGuidesInLine; indentLvl++) {
const indentGuide = (indentLvl - 1) * indentSize + 1;
const isActive =
// Disable active indent guide if there are bracket guides.
(this._bracketPairGuideOptions.highlightActiveIndentation === 'always' || bracketGuidesInLine.length === 0) &&
activeIndentStartLineNumber <= lineNumber &&
lineNumber <= activeIndentEndLineNumber &&
indentLvl === activeIndentLevel;
lineGuides.push(...bracketGuidesInLineQueue.takeWhile(g => g.visibleColumn < indentGuide) || []);
const peeked = bracketGuidesInLineQueue.peek();
if (!peeked || peeked.visibleColumn !== indentGuide || peeked.horizontalLine) {
lineGuides.push(new IndentGuide(indentGuide, -1, `core-guide-indent lvl-${(indentLvl - 1) % 30}` + (isActive ? ' indent-active' : ''), null, -1, -1));
}
}
lineGuides.push(...bracketGuidesInLineQueue.takeWhile(g => true) || []);
}
return result;
}
render(startLineNumber, lineNumber) {
if (!this._renderResult) {
return '';
}
const lineIndex = lineNumber - startLineNumber;
if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
return '';
}
return this._renderResult[lineIndex];
}
}
function transparentToUndefined(color) {
if (color && color.isTransparent()) {
return undefined;
}
return color;
}
registerThemingParticipant((theme, collector) => {
const colors = [
{ bracketColor: editorBracketHighlightingForeground1, guideColor: editorBracketPairGuideBackground1, guideColorActive: editorBracketPairGuideActiveBackground1 },
{ bracketColor: editorBracketHighlightingForeground2, guideColor: editorBracketPairGuideBackground2, guideColorActive: editorBracketPairGuideActiveBackground2 },
{ bracketColor: editorBracketHighlightingForeground3, guideColor: editorBracketPairGuideBackground3, guideColorActive: editorBracketPairGuideActiveBackground3 },
{ bracketColor: editorBracketHighlightingForeground4, guideColor: editorBracketPairGuideBackground4, guideColorActive: editorBracketPairGuideActiveBackground4 },
{ bracketColor: editorBracketHighlightingForeground5, guideColor: editorBracketPairGuideBackground5, guideColorActive: editorBracketPairGuideActiveBackground5 },
{ bracketColor: editorBracketHighlightingForeground6, guideColor: editorBracketPairGuideBackground6, guideColorActive: editorBracketPairGuideActiveBackground6 }
];
const colorProvider = new BracketPairGuidesClassNames();
const indentColors = [
{ indentColor: editorIndentGuide1, indentColorActive: editorActiveIndentGuide1 },
{ indentColor: editorIndentGuide2, indentColorActive: editorActiveIndentGuide2 },
{ indentColor: editorIndentGuide3, indentColorActive: editorActiveIndentGuide3 },
{ indentColor: editorIndentGuide4, indentColorActive: editorActiveIndentGuide4 },
{ indentColor: editorIndentGuide5, indentColorActive: editorActiveIndentGuide5 },
{ indentColor: editorIndentGuide6, indentColorActive: editorActiveIndentGuide6 },
];
const colorValues = colors
.map(c => {
const bracketColor = theme.getColor(c.bracketColor);
const guideColor = theme.getColor(c.guideColor);
const guideColorActive = theme.getColor(c.guideColorActive);
const effectiveGuideColor = transparentToUndefined(transparentToUndefined(guideColor) ?? bracketColor?.transparent(0.3));
const effectiveGuideColorActive = transparentToUndefined(transparentToUndefined(guideColorActive) ?? bracketColor);
if (!effectiveGuideColor || !effectiveGuideColorActive) {
return undefined;
}
return {
guideColor: effectiveGuideColor,
guideColorActive: effectiveGuideColorActive,
};
})
.filter(isDefined);
const indentColorValues = indentColors
.map(c => {
const indentColor = theme.getColor(c.indentColor);
const indentColorActive = theme.getColor(c.indentColorActive);
const effectiveIndentColor = transparentToUndefined(indentColor);
const effectiveIndentColorActive = transparentToUndefined(indentColorActive);
if (!effectiveIndentColor || !effectiveIndentColorActive) {
return undefined;
}
return {
indentColor: effectiveIndentColor,
indentColorActive: effectiveIndentColorActive,
};
})
.filter(isDefined);
if (colorValues.length > 0) {
for (let level = 0; level < 30; level++) {
const colors = colorValues[level % colorValues.length];
collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')} { --guide-color: ${colors.guideColor}; --guide-color-active: ${colors.guideColorActive}; }`);
}
collector.addRule(`.monaco-editor .vertical { box-shadow: 1px 0 0 0 var(--guide-color) inset; }`);
collector.addRule(`.monaco-editor .horizontal-top { border-top: 1px solid var(--guide-color); }`);
collector.addRule(`.monaco-editor .horizontal-bottom { border-bottom: 1px solid var(--guide-color); }`);
collector.addRule(`.monaco-editor .vertical.${colorProvider.activeClassName} { box-shadow: 1px 0 0 0 var(--guide-color-active) inset; }`);
collector.addRule(`.monaco-editor .horizontal-top.${colorProvider.activeClassName} { border-top: 1px solid var(--guide-color-active); }`);
collector.addRule(`.monaco-editor .horizontal-bottom.${colorProvider.activeClassName} { border-bottom: 1px solid var(--guide-color-active); }`);
}
if (indentColorValues.length > 0) {
for (let level = 0; level < 30; level++) {
const colors = indentColorValues[level % indentColorValues.length];
collector.addRule(`.monaco-editor .lines-content .core-guide-indent.lvl-${level} { --indent-color: ${colors.indentColor}; --indent-color-active: ${colors.indentColorActive}; }`);
}
collector.addRule(`.monaco-editor .lines-content .core-guide-indent { box-shadow: 1px 0 0 0 var(--indent-color) inset; }`);
collector.addRule(`.monaco-editor .lines-content .core-guide-indent.indent-active { box-shadow: 1px 0 0 0 var(--indent-color-active) inset; }`);
}
});