UNPKG

monaco-editor-core

Version:

A browser based code editor

194 lines (193 loc) 6.79 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { BugIndicatingError } from '../../../base/common/errors.js'; /** * A range of offsets (0-based). */ export class OffsetRange { static addRange(range, sortedRanges) { let i = 0; while (i < sortedRanges.length && sortedRanges[i].endExclusive < range.start) { i++; } let j = i; while (j < sortedRanges.length && sortedRanges[j].start <= range.endExclusive) { j++; } if (i === j) { sortedRanges.splice(i, 0, range); } else { const start = Math.min(range.start, sortedRanges[i].start); const end = Math.max(range.endExclusive, sortedRanges[j - 1].endExclusive); sortedRanges.splice(i, j - i, new OffsetRange(start, end)); } } static tryCreate(start, endExclusive) { if (start > endExclusive) { return undefined; } return new OffsetRange(start, endExclusive); } static ofLength(length) { return new OffsetRange(0, length); } static ofStartAndLength(start, length) { return new OffsetRange(start, start + length); } constructor(start, endExclusive) { this.start = start; this.endExclusive = endExclusive; if (start > endExclusive) { throw new BugIndicatingError(`Invalid range: ${this.toString()}`); } } get isEmpty() { return this.start === this.endExclusive; } delta(offset) { return new OffsetRange(this.start + offset, this.endExclusive + offset); } deltaStart(offset) { return new OffsetRange(this.start + offset, this.endExclusive); } deltaEnd(offset) { return new OffsetRange(this.start, this.endExclusive + offset); } get length() { return this.endExclusive - this.start; } toString() { return `[${this.start}, ${this.endExclusive})`; } contains(offset) { return this.start <= offset && offset < this.endExclusive; } /** * for all numbers n: range1.contains(n) or range2.contains(n) => range1.join(range2).contains(n) * The joined range is the smallest range that contains both ranges. */ join(other) { return new OffsetRange(Math.min(this.start, other.start), Math.max(this.endExclusive, other.endExclusive)); } /** * for all numbers n: range1.contains(n) and range2.contains(n) <=> range1.intersect(range2).contains(n) * * The resulting range is empty if the ranges do not intersect, but touch. * If the ranges don't even touch, the result is undefined. */ intersect(other) { const start = Math.max(this.start, other.start); const end = Math.min(this.endExclusive, other.endExclusive); if (start <= end) { return new OffsetRange(start, end); } return undefined; } intersects(other) { const start = Math.max(this.start, other.start); const end = Math.min(this.endExclusive, other.endExclusive); return start < end; } isBefore(other) { return this.endExclusive <= other.start; } isAfter(other) { return this.start >= other.endExclusive; } slice(arr) { return arr.slice(this.start, this.endExclusive); } substring(str) { return str.substring(this.start, this.endExclusive); } /** * Returns the given value if it is contained in this instance, otherwise the closest value that is contained. * The range must not be empty. */ clip(value) { if (this.isEmpty) { throw new BugIndicatingError(`Invalid clipping range: ${this.toString()}`); } return Math.max(this.start, Math.min(this.endExclusive - 1, value)); } /** * Returns `r := value + k * length` such that `r` is contained in this range. * The range must not be empty. * * E.g. `[5, 10).clipCyclic(10) === 5`, `[5, 10).clipCyclic(11) === 6` and `[5, 10).clipCyclic(4) === 9`. */ clipCyclic(value) { if (this.isEmpty) { throw new BugIndicatingError(`Invalid clipping range: ${this.toString()}`); } if (value < this.start) { return this.endExclusive - ((this.start - value) % this.length); } if (value >= this.endExclusive) { return this.start + ((value - this.start) % this.length); } return value; } forEach(f) { for (let i = this.start; i < this.endExclusive; i++) { f(i); } } } export class OffsetRangeSet { constructor() { this._sortedRanges = []; } addRange(range) { let i = 0; while (i < this._sortedRanges.length && this._sortedRanges[i].endExclusive < range.start) { i++; } let j = i; while (j < this._sortedRanges.length && this._sortedRanges[j].start <= range.endExclusive) { j++; } if (i === j) { this._sortedRanges.splice(i, 0, range); } else { const start = Math.min(range.start, this._sortedRanges[i].start); const end = Math.max(range.endExclusive, this._sortedRanges[j - 1].endExclusive); this._sortedRanges.splice(i, j - i, new OffsetRange(start, end)); } } toString() { return this._sortedRanges.map(r => r.toString()).join(', '); } /** * Returns of there is a value that is contained in this instance and the given range. */ intersectsStrict(other) { // TODO use binary search let i = 0; while (i < this._sortedRanges.length && this._sortedRanges[i].endExclusive <= other.start) { i++; } return i < this._sortedRanges.length && this._sortedRanges[i].start < other.endExclusive; } intersectWithRange(other) { // TODO use binary search + slice const result = new OffsetRangeSet(); for (const range of this._sortedRanges) { const intersection = range.intersect(other); if (intersection) { result.addRange(intersection); } } return result; } intersectWithRangeLength(other) { return this.intersectWithRange(other).length; } get length() { return this._sortedRanges.reduce((prev, cur) => prev + cur.length, 0); } }