Path: blob/main/extensions/copilot/test/simulation/fixtures/edit/4302.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation and GitHub. All rights reserved.2*--------------------------------------------------------------------------------------------*/3import { CharCode } from '../../../util/vs/common/charCode';45export interface ISimpleTextModel {6getLineCount(): number;7getLineContent(lineNumber: number): string;8getOptions(): { tabSize: number };9}1011export function computeRanges(model: ISimpleTextModel, languageId: string): FoldingRegions {12const offSide = [13'clojure',14'coffeescript',15'fsharp',16'latex',17'markdown',18'pug',19'python',20'sql',21'yaml',22].includes(languageId.toLowerCase());23return _computeRanges(model, offSide);24}2526function _computeRanges(model: ISimpleTextModel, offSide: boolean): FoldingRegions {27const tabSize = model.getOptions().tabSize;28const result = new RangesCollector();29const previousRegions: PreviousRegion[] = [];30const line = model.getLineCount() + 1;31previousRegions.push({ indent: -1, endAbove: line, line }); // sentinel, to make sure there's at least one entry32for (let line = model.getLineCount(); line > 0; line--) {33const lineContent = model.getLineContent(line);34const indent = computeIndentLevel(lineContent, tabSize);35let previous = previousRegions[previousRegions.length - 1];36if (indent === -1) {37if (offSide) {38// for offSide languages, empty lines are associated to the previous block39// note: the next block is already written to the results, so this only40// impacts the end position of the block before41previous.endAbove = line;42}43continue; // only whitespace44}45if (previous.indent > indent) {46// discard all regions with larger indent47do {48previousRegions.pop();49previous = previousRegions[previousRegions.length - 1];50} while (previous.indent > indent);51// new folding range52const endLineNumber = previous.endAbove - 1;53if (endLineNumber - line >= 1) { // needs at east size 154result.insertFirst(line, endLineNumber, indent);55}56}57if (previous.indent === indent) {58previous.endAbove = line;59} else { // previous.indent < indent60// new region with a bigger indent61previousRegions.push({ indent, endAbove: line, line });62}63}64return result.toIndentRanges();65}6667interface PreviousRegion {68indent: number; // indent or -2 if a marker69endAbove: number; // end line number for the region above70line: number; // start line of the region. Only used for marker regions.71}7273const MAX_FOLDING_REGIONS = 0xFFFF;74const MAX_LINE_NUMBER = 0xFFFFFF;75const MASK_INDENT = 0xFF000000;7677class RangesCollector {78private readonly _startIndexes: number[];79private readonly _endIndexes: number[];80private readonly _indentOccurrences: number[];81private _length: number;8283constructor() {84this._startIndexes = [];85this._endIndexes = [];86this._indentOccurrences = [];87this._length = 0;88}89public insertFirst(startLineNumber: number, endLineNumber: number, indent: number) {90if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {91return;92}93const index = this._length;94this._startIndexes[index] = startLineNumber;95this._endIndexes[index] = endLineNumber;96this._length++;97if (indent < 1000) {98this._indentOccurrences[indent] = (this._indentOccurrences[indent] || 0) + 1;99}100}101public toIndentRanges() {102// reverse and create arrays of the exact length103const startIndexes = new Uint32Array(this._length);104const endIndexes = new Uint32Array(this._length);105for (let i = this._length - 1, k = 0; i >= 0; i--, k++) {106startIndexes[k] = this._startIndexes[i];107endIndexes[k] = this._endIndexes[i];108}109return new FoldingRegions(startIndexes, endIndexes);110}111}112113/**114* Returns:115* - -1 => the line consists of whitespace116* - otherwise => the indent level is returned value117*/118export function computeIndentLevel(line: string, tabSize: number): number {119let indent = 0;120let i = 0;121const len = line.length;122while (i < len) {123const chCode = line.charCodeAt(i);124if (chCode === CharCode.Space) {125indent++;126} else if (chCode === CharCode.Tab) {127indent = indent - indent % tabSize + tabSize;128} else {129break;130}131i++;132}133134if (i === len) {135return -1; // line only consists of whitespace136}137return indent;138}139140export class FoldingRegions {141private readonly _startIndexes: Uint32Array;142private readonly _endIndexes: Uint32Array;143144private _parentsComputed: boolean;145146constructor(startIndexes: Uint32Array, endIndexes: Uint32Array) {147this._startIndexes = startIndexes;148this._endIndexes = endIndexes;149this._parentsComputed = false;150this.ensureParentIndices();151}152private ensureParentIndices() {153if (!this._parentsComputed) {154this._parentsComputed = true;155const parentIndexes: number[] = [];156const isInsideLast = (startLineNumber: number, endLineNumber: number) => {157const index = parentIndexes[parentIndexes.length - 1];158return this.getStartLineNumber(index) <= startLineNumber && this.getEndLineNumber(index) >= endLineNumber;159};160for (let i = 0, len = this._startIndexes.length; i < len; i++) {161const startLineNumber = this._startIndexes[i];162const endLineNumber = this._endIndexes[i];163if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {164throw new Error('startLineNumber or endLineNumber must not exceed ' + MAX_LINE_NUMBER);165}166while (parentIndexes.length > 0 && !isInsideLast(startLineNumber, endLineNumber)) {167parentIndexes.pop();168}169const parentIndex = parentIndexes.length > 0 ? parentIndexes[parentIndexes.length - 1] : -1;170parentIndexes.push(i);171this._startIndexes[i] = startLineNumber + ((parentIndex & 0xFF) << 24);172this._endIndexes[i] = endLineNumber + ((parentIndex & 0xFF00) << 16);173}174}175}176public get length(): number {177return this._startIndexes.length;178}179public getStartLineNumber(index: number): number {180return this._startIndexes[index] & MAX_LINE_NUMBER;181}182public getEndLineNumber(index: number): number {183return this._endIndexes[index] & MAX_LINE_NUMBER;184}185// public toRegion(index: number): FoldingRegion {186// return new FoldingRegion(this, index);187// }188public getParentIndex(index: number) {189this.ensureParentIndices();190const parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16);191if (parent === MAX_FOLDING_REGIONS) {192return -1;193}194return parent;195}196public contains(index: number, line: number) {197return this.getStartLineNumber(index) <= line && this.getEndLineNumber(index) >= line;198}199private findIndex(line: number) {200let low = 0, high = this._startIndexes.length;201if (high === 0) {202return -1; // no children203}204while (low < high) {205const mid = Math.floor((low + high) / 2);206if (line < this.getStartLineNumber(mid)) {207high = mid;208} else {209low = mid + 1;210}211}212return low - 1;213}214public findRange(line: number): number {215let index = this.findIndex(line);216if (index >= 0) {217const endLineNumber = this.getEndLineNumber(index);218if (endLineNumber >= line) {219return index;220}221index = this.getParentIndex(index);222while (index !== -1) {223if (this.contains(index, line)) {224return index;225}226index = this.getParentIndex(index);227}228}229return -1;230}231// public toString() {232// const res: string[] = [];233// for (let i = 0; i < this.length; i++) {234// res[i] = `[${foldSourceAbbr[this.getSource(i)]}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;235// }236// return res.join(', ');237// }238// public toFoldRange(index: number): FoldRange {239// return <FoldRange>{240// startLineNumber: this._startIndexes[index] & MAX_LINE_NUMBER,241// endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER,242// type: this._types ? this._types[index] : undefined,243// isCollapsed: this.isCollapsed(index),244// source: this.getSource(index)245// };246// }247}248249250