| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { findLast } from '../../../base/common/arrays.js';
- import * as strings from '../../../base/common/strings.js';
- import { CursorColumns } from '../core/cursorColumns.js';
- import { Range } from '../core/range.js';
- import { TextModelPart } from './textModelPart.js';
- import { computeIndentLevel } from './utils.js';
- import { HorizontalGuidesState, IndentGuide, IndentGuideHorizontalLine } from '../textModelGuides.js';
- export class GuidesTextModelPart extends TextModelPart {
- constructor(textModel, languageConfigurationService) {
- super();
- this.textModel = textModel;
- this.languageConfigurationService = languageConfigurationService;
- }
- getLanguageConfiguration(languageId) {
- return this.languageConfigurationService.getLanguageConfiguration(languageId);
- }
- _computeIndentLevel(lineIndex) {
- return computeIndentLevel(this.textModel.getLineContent(lineIndex + 1), this.textModel.getOptions().tabSize);
- }
- getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber) {
- this.assertNotDisposed();
- const lineCount = this.textModel.getLineCount();
- if (lineNumber < 1 || lineNumber > lineCount) {
- throw new Error('Illegal value for lineNumber');
- }
- const foldingRules = this.getLanguageConfiguration(this.textModel.getLanguageId()).foldingRules;
- const offSide = Boolean(foldingRules && foldingRules.offSide);
- let up_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
- let up_aboveContentLineIndent = -1;
- let up_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
- let up_belowContentLineIndent = -1;
- const up_resolveIndents = (lineNumber) => {
- if (up_aboveContentLineIndex !== -1 &&
- (up_aboveContentLineIndex === -2 ||
- up_aboveContentLineIndex > lineNumber - 1)) {
- up_aboveContentLineIndex = -1;
- up_aboveContentLineIndent = -1;
- // must find previous line with content
- for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
- const indent = this._computeIndentLevel(lineIndex);
- if (indent >= 0) {
- up_aboveContentLineIndex = lineIndex;
- up_aboveContentLineIndent = indent;
- break;
- }
- }
- }
- if (up_belowContentLineIndex === -2) {
- up_belowContentLineIndex = -1;
- up_belowContentLineIndent = -1;
- // must find next line with content
- for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
- const indent = this._computeIndentLevel(lineIndex);
- if (indent >= 0) {
- up_belowContentLineIndex = lineIndex;
- up_belowContentLineIndent = indent;
- break;
- }
- }
- }
- };
- let down_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
- let down_aboveContentLineIndent = -1;
- let down_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
- let down_belowContentLineIndent = -1;
- const down_resolveIndents = (lineNumber) => {
- if (down_aboveContentLineIndex === -2) {
- down_aboveContentLineIndex = -1;
- down_aboveContentLineIndent = -1;
- // must find previous line with content
- for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
- const indent = this._computeIndentLevel(lineIndex);
- if (indent >= 0) {
- down_aboveContentLineIndex = lineIndex;
- down_aboveContentLineIndent = indent;
- break;
- }
- }
- }
- if (down_belowContentLineIndex !== -1 &&
- (down_belowContentLineIndex === -2 ||
- down_belowContentLineIndex < lineNumber - 1)) {
- down_belowContentLineIndex = -1;
- down_belowContentLineIndent = -1;
- // must find next line with content
- for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
- const indent = this._computeIndentLevel(lineIndex);
- if (indent >= 0) {
- down_belowContentLineIndex = lineIndex;
- down_belowContentLineIndent = indent;
- break;
- }
- }
- }
- };
- let startLineNumber = 0;
- let goUp = true;
- let endLineNumber = 0;
- let goDown = true;
- let indent = 0;
- let initialIndent = 0;
- for (let distance = 0; goUp || goDown; distance++) {
- const upLineNumber = lineNumber - distance;
- const downLineNumber = lineNumber + distance;
- if (distance > 1 && (upLineNumber < 1 || upLineNumber < minLineNumber)) {
- goUp = false;
- }
- if (distance > 1 &&
- (downLineNumber > lineCount || downLineNumber > maxLineNumber)) {
- goDown = false;
- }
- if (distance > 50000) {
- // stop processing
- goUp = false;
- goDown = false;
- }
- let upLineIndentLevel = -1;
- if (goUp && upLineNumber >= 1) {
- // compute indent level going up
- const currentIndent = this._computeIndentLevel(upLineNumber - 1);
- if (currentIndent >= 0) {
- // This line has content (besides whitespace)
- // Use the line's indent
- up_belowContentLineIndex = upLineNumber - 1;
- up_belowContentLineIndent = currentIndent;
- upLineIndentLevel = Math.ceil(currentIndent / this.textModel.getOptions().indentSize);
- }
- else {
- up_resolveIndents(upLineNumber);
- upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent);
- }
- }
- let downLineIndentLevel = -1;
- if (goDown && downLineNumber <= lineCount) {
- // compute indent level going down
- const currentIndent = this._computeIndentLevel(downLineNumber - 1);
- if (currentIndent >= 0) {
- // This line has content (besides whitespace)
- // Use the line's indent
- down_aboveContentLineIndex = downLineNumber - 1;
- down_aboveContentLineIndent = currentIndent;
- downLineIndentLevel = Math.ceil(currentIndent / this.textModel.getOptions().indentSize);
- }
- else {
- down_resolveIndents(downLineNumber);
- downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent);
- }
- }
- if (distance === 0) {
- initialIndent = upLineIndentLevel;
- continue;
- }
- if (distance === 1) {
- if (downLineNumber <= lineCount &&
- downLineIndentLevel >= 0 &&
- initialIndent + 1 === downLineIndentLevel) {
- // This is the beginning of a scope, we have special handling here, since we want the
- // child scope indent to be active, not the parent scope
- goUp = false;
- startLineNumber = downLineNumber;
- endLineNumber = downLineNumber;
- indent = downLineIndentLevel;
- continue;
- }
- if (upLineNumber >= 1 &&
- upLineIndentLevel >= 0 &&
- upLineIndentLevel - 1 === initialIndent) {
- // This is the end of a scope, just like above
- goDown = false;
- startLineNumber = upLineNumber;
- endLineNumber = upLineNumber;
- indent = upLineIndentLevel;
- continue;
- }
- startLineNumber = lineNumber;
- endLineNumber = lineNumber;
- indent = initialIndent;
- if (indent === 0) {
- // No need to continue
- return { startLineNumber, endLineNumber, indent };
- }
- }
- if (goUp) {
- if (upLineIndentLevel >= indent) {
- startLineNumber = upLineNumber;
- }
- else {
- goUp = false;
- }
- }
- if (goDown) {
- if (downLineIndentLevel >= indent) {
- endLineNumber = downLineNumber;
- }
- else {
- goDown = false;
- }
- }
- }
- return { startLineNumber, endLineNumber, indent };
- }
- getLinesBracketGuides(startLineNumber, endLineNumber, activePosition, options) {
- var _a;
- const result = [];
- for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
- result.push([]);
- }
- // If requested, this could be made configurable.
- const includeSingleLinePairs = true;
- const bracketPairs = this.textModel.bracketPairs.getBracketPairsInRangeWithMinIndentation(new Range(startLineNumber, 1, endLineNumber, this.textModel.getLineMaxColumn(endLineNumber)));
- let activeBracketPairRange = undefined;
- if (activePosition && bracketPairs.length > 0) {
- const bracketsContainingActivePosition = (startLineNumber <= activePosition.lineNumber &&
- activePosition.lineNumber <= endLineNumber
- // We don't need to query the brackets again if the cursor is in the viewport
- ? bracketPairs
- : this.textModel.bracketPairs.getBracketPairsInRange(Range.fromPositions(activePosition))).filter((bp) => Range.strictContainsPosition(bp.range, activePosition));
- activeBracketPairRange = (_a = findLast(bracketsContainingActivePosition, (i) => includeSingleLinePairs || i.range.startLineNumber !== i.range.endLineNumber)) === null || _a === void 0 ? void 0 : _a.range;
- }
- const independentColorPoolPerBracketType = this.textModel.getOptions().bracketPairColorizationOptions.independentColorPoolPerBracketType;
- const colorProvider = new BracketPairGuidesClassNames();
- for (const pair of bracketPairs) {
- /*
- {
- |
- }
- {
- |
- ----}
- ____{
- |test
- ----}
- renderHorizontalEndLineAtTheBottom:
- {
- |
- |x}
- --
- renderHorizontalEndLineAtTheBottom:
- ____{
- |test
- | x }
- ----
- */
- if (!pair.closingBracketRange) {
- continue;
- }
- const isActive = activeBracketPairRange && pair.range.equalsRange(activeBracketPairRange);
- if (!isActive && !options.includeInactive) {
- continue;
- }
- const className = colorProvider.getInlineClassName(pair.nestingLevel, pair.nestingLevelOfEqualBracketType, independentColorPoolPerBracketType) +
- (options.highlightActive && isActive
- ? ' ' + colorProvider.activeClassName
- : '');
- const start = pair.openingBracketRange.getStartPosition();
- const end = pair.closingBracketRange.getStartPosition();
- const horizontalGuides = options.horizontalGuides === HorizontalGuidesState.Enabled || (options.horizontalGuides === HorizontalGuidesState.EnabledForActive && isActive);
- if (pair.range.startLineNumber === pair.range.endLineNumber) {
- if (includeSingleLinePairs && horizontalGuides) {
- result[pair.range.startLineNumber - startLineNumber].push(new IndentGuide(-1, pair.openingBracketRange.getEndPosition().column, className, new IndentGuideHorizontalLine(false, end.column), -1, -1));
- }
- continue;
- }
- const endVisibleColumn = this.getVisibleColumnFromPosition(end);
- const startVisibleColumn = this.getVisibleColumnFromPosition(pair.openingBracketRange.getStartPosition());
- const guideVisibleColumn = Math.min(startVisibleColumn, endVisibleColumn, pair.minVisibleColumnIndentation + 1);
- let renderHorizontalEndLineAtTheBottom = false;
- const firstNonWsIndex = strings.firstNonWhitespaceIndex(this.textModel.getLineContent(pair.closingBracketRange.startLineNumber));
- const hasTextBeforeClosingBracket = firstNonWsIndex < pair.closingBracketRange.startColumn - 1;
- if (hasTextBeforeClosingBracket) {
- renderHorizontalEndLineAtTheBottom = true;
- }
- const visibleGuideStartLineNumber = Math.max(start.lineNumber, startLineNumber);
- const visibleGuideEndLineNumber = Math.min(end.lineNumber, endLineNumber);
- const offset = renderHorizontalEndLineAtTheBottom ? 1 : 0;
- for (let l = visibleGuideStartLineNumber; l < visibleGuideEndLineNumber + offset; l++) {
- result[l - startLineNumber].push(new IndentGuide(guideVisibleColumn, -1, className, null, l === start.lineNumber ? start.column : -1, l === end.lineNumber ? end.column : -1));
- }
- if (horizontalGuides) {
- if (start.lineNumber >= startLineNumber && startVisibleColumn > guideVisibleColumn) {
- result[start.lineNumber - startLineNumber].push(new IndentGuide(guideVisibleColumn, -1, className, new IndentGuideHorizontalLine(false, start.column), -1, -1));
- }
- if (end.lineNumber <= endLineNumber && endVisibleColumn > guideVisibleColumn) {
- result[end.lineNumber - startLineNumber].push(new IndentGuide(guideVisibleColumn, -1, className, new IndentGuideHorizontalLine(!renderHorizontalEndLineAtTheBottom, end.column), -1, -1));
- }
- }
- }
- for (const guides of result) {
- guides.sort((a, b) => a.visibleColumn - b.visibleColumn);
- }
- return result;
- }
- getVisibleColumnFromPosition(position) {
- return (CursorColumns.visibleColumnFromColumn(this.textModel.getLineContent(position.lineNumber), position.column, this.textModel.getOptions().tabSize) + 1);
- }
- getLinesIndentGuides(startLineNumber, endLineNumber) {
- this.assertNotDisposed();
- const lineCount = this.textModel.getLineCount();
- if (startLineNumber < 1 || startLineNumber > lineCount) {
- throw new Error('Illegal value for startLineNumber');
- }
- if (endLineNumber < 1 || endLineNumber > lineCount) {
- throw new Error('Illegal value for endLineNumber');
- }
- const options = this.textModel.getOptions();
- const foldingRules = this.getLanguageConfiguration(this.textModel.getLanguageId()).foldingRules;
- const offSide = Boolean(foldingRules && foldingRules.offSide);
- const result = new Array(endLineNumber - startLineNumber + 1);
- let aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
- let aboveContentLineIndent = -1;
- let belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
- let belowContentLineIndent = -1;
- for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
- const resultIndex = lineNumber - startLineNumber;
- const currentIndent = this._computeIndentLevel(lineNumber - 1);
- if (currentIndent >= 0) {
- // This line has content (besides whitespace)
- // Use the line's indent
- aboveContentLineIndex = lineNumber - 1;
- aboveContentLineIndent = currentIndent;
- result[resultIndex] = Math.ceil(currentIndent / options.indentSize);
- continue;
- }
- if (aboveContentLineIndex === -2) {
- aboveContentLineIndex = -1;
- aboveContentLineIndent = -1;
- // must find previous line with content
- for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
- const indent = this._computeIndentLevel(lineIndex);
- if (indent >= 0) {
- aboveContentLineIndex = lineIndex;
- aboveContentLineIndent = indent;
- break;
- }
- }
- }
- if (belowContentLineIndex !== -1 &&
- (belowContentLineIndex === -2 || belowContentLineIndex < lineNumber - 1)) {
- belowContentLineIndex = -1;
- belowContentLineIndent = -1;
- // must find next line with content
- for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
- const indent = this._computeIndentLevel(lineIndex);
- if (indent >= 0) {
- belowContentLineIndex = lineIndex;
- belowContentLineIndent = indent;
- break;
- }
- }
- }
- result[resultIndex] = this._getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent);
- }
- return result;
- }
- _getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent) {
- const options = this.textModel.getOptions();
- if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) {
- // At the top or bottom of the file
- return 0;
- }
- else if (aboveContentLineIndent < belowContentLineIndent) {
- // we are inside the region above
- return 1 + Math.floor(aboveContentLineIndent / options.indentSize);
- }
- else if (aboveContentLineIndent === belowContentLineIndent) {
- // we are in between two regions
- return Math.ceil(belowContentLineIndent / options.indentSize);
- }
- else {
- if (offSide) {
- // same level as region below
- return Math.ceil(belowContentLineIndent / options.indentSize);
- }
- else {
- // we are inside the region that ends below
- return 1 + Math.floor(belowContentLineIndent / options.indentSize);
- }
- }
- }
- }
- export class BracketPairGuidesClassNames {
- constructor() {
- this.activeClassName = 'indent-active';
- }
- getInlineClassName(nestingLevel, nestingLevelOfEqualBracketType, independentColorPoolPerBracketType) {
- return this.getInlineClassNameOfLevel(independentColorPoolPerBracketType ? nestingLevelOfEqualBracketType : nestingLevel);
- }
- getInlineClassNameOfLevel(level) {
- // To support a dynamic amount of colors up to 6 colors,
- // we use a number that is a lcm of all numbers from 1 to 6.
- return `bracket-indent-guide lvl-${level % 30}`;
- }
- }
|