monaco-editor-core
Version:
A browser based code editor
194 lines (193 loc) • 6.79 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 { 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);
}
}