or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

changes.mdcharacter-utils.mdeditor-state.mdextensions.mdindex.mdrange-sets.mdselection.mdtext.mdtransactions.md
tile.json

range-sets.mddocs/

Range Sets

Efficient data structures for managing non-overlapping ranges with associated values, used for decorations and markers.

Capabilities

RangeSet Class

Immutable data structure for storing and querying non-overlapping ranges with associated values.

/**
 * A range set stores a collection of ranges with associated values.
 * The ranges are non-overlapping and stored in sorted order.
 */
class RangeSet<T extends RangeValue> {
  /** Create a range set from an array of ranges */
  static of<T extends RangeValue>(ranges: readonly Range<T>[] | Range<T>, sort?: boolean): RangeSet<T>;
  
  /** An empty range set */
  static empty: RangeSet<any>;
  
  /** The number of ranges in this set */
  readonly size: number;
  
  /** Whether this range set is empty */
  readonly empty: boolean;
  
  /** Create a new range set by updating this one */
  update(updateSpec: RangeSetUpdateSpec<T>): RangeSet<T>;
  
  /** Map this range set through a set of changes */
  map(changes: ChangeDesc): RangeSet<T>;
  
  /** Iterate over ranges that overlap with the given range */
  between(from: number, to: number, f: (from: number, to: number, value: T) => void | false): void;
  
  /** Get an iterator for this range set */
  iter(from?: number): RangeCursor<T>;
  
  /** Compare this range set with another */
  compare(other: RangeSet<T>, textDiff?: ChangeDesc, comparator?: RangeComparator<T>, oldLen?: number): void;
}

interface RangeSetUpdateSpec<T extends RangeValue> {
  /** Ranges to add */
  add?: readonly Range<T>[];
  
  /** Ranges to remove (by filtering) */
  filter?: (from: number, to: number, value: T) => boolean;
  
  /** Ranges of text to filter out entirely */
  filterFrom?: number;
  
  /** End of range to filter out */
  filterTo?: number;
}

Usage Examples:

import { RangeSet, RangeValue, Range } from "@codemirror/state";

// Define a custom range value
class HighlightValue extends RangeValue {
  constructor(public color: string) {
    super();
  }
  
  eq(other: RangeValue) {
    return other instanceof HighlightValue && other.color === this.color;
  }
}

// Create ranges
const yellowHighlight = new HighlightValue("yellow");
const blueHighlight = new HighlightValue("blue");

const ranges = [
  yellowHighlight.range(5, 10),
  blueHighlight.range(15, 20),
  yellowHighlight.range(25, 30)
];

// Create range set
const rangeSet = RangeSet.of(ranges);

console.log(rangeSet.size); // 3
console.log(rangeSet.empty); // false

// Iterate over ranges in a specific area
rangeSet.between(8, 18, (from, to, value) => {
  console.log(`Range ${from}-${to}: ${value.color}`);
  // Output: "Range 8-10: yellow" and "Range 15-18: blue"
});

RangeValue Base Class

Abstract base class for values associated with ranges.

/**
 * Each range is associated with a value, which must inherit from this class.
 */
abstract class RangeValue {
  /** Compare this value with another value */
  eq(other: RangeValue): boolean;
  
  /** The bias value at the start of the range */
  startSide: number;
  
  /** The bias value at the end of the range */
  endSide: number;
  
  /** The mode with which the location of the range should be mapped when from and to are the same */
  mapMode: MapMode;
  
  /** Determines whether this value marks a point range */
  point: boolean;
  
  /** Create a Range with this value */
  range(from: number, to?: number): Range<this>;
}

Usage Examples:

// Custom range value for bookmarks
class BookmarkValue extends RangeValue {
  constructor(public id: string, public label: string) {
    super();
    this.point = true; // This is a point decoration
    this.startSide = 1; // Appears after other content at same position
  }
  
  eq(other: RangeValue) {
    return other instanceof BookmarkValue && 
           other.id === this.id && 
           other.label === this.label;
  }
}

// Custom range value for error highlighting
class ErrorValue extends RangeValue {
  constructor(public message: string, public severity: "error" | "warning") {
    super();
    this.startSide = -1; // Appears before other content
    this.endSide = 1;    // Ends after other content
  }
  
  eq(other: RangeValue) {
    return other instanceof ErrorValue && 
           other.message === this.message && 
           other.severity === this.severity;
  }
}

// Create and use custom ranges
const bookmark = new BookmarkValue("bm1", "Important Note");
const error = new ErrorValue("Syntax error", "error");

const bookmarkRange = bookmark.range(15);     // Point range at position 15
const errorRange = error.range(20, 25);       // Range from 20 to 25

Range Class

Represents a single range with an associated value.

/**
 * A range associates a value with a range of positions.
 */
class Range<T extends RangeValue> {
  /** The range's start position */
  readonly from: number;
  
  /** Its end position */
  readonly to: number;
  
  /** The value associated with this range */
  readonly value: T;
  
  /** @internal - Create a range (use RangeValue.range() instead) */
  static create<T extends RangeValue>(from: number, to: number, value: T): Range<T>;
}

Range Set Operations

Methods for creating and manipulating range sets.

/**
 * Update a range set by adding, removing, or filtering ranges
 * @param updateSpec Specification of what to change
 */
update(updateSpec: RangeSetUpdateSpec<T>): RangeSet<T>;

/**
 * Map this range set through a set of changes, updating all positions
 * @param changes The changes to map through
 */
map(changes: ChangeDesc): RangeSet<T>;

/**
 * Iterate over ranges that overlap with the given range
 * @param from Start position
 * @param to End position  
 * @param f Callback for each overlapping range
 */
between(from: number, to: number, f: (from: number, to: number, value: T) => void | false): void;

Usage Examples:

const rangeSet = RangeSet.of([
  new HighlightValue("yellow").range(5, 10),
  new HighlightValue("blue").range(15, 20)
]);

// Add new ranges
const updated = rangeSet.update({
  add: [new HighlightValue("red").range(25, 30)]
});

// Filter ranges
const filtered = rangeSet.update({
  filter: (from, to, value) => value.color !== "blue" // Remove blue highlights
});

// Filter by position
const positionFiltered = rangeSet.update({
  filterFrom: 12,
  filterTo: 18 // Remove ranges that overlap with positions 12-18
});

// Map through changes
const changes = ChangeSet.of([{from: 3, insert: "NEW "}], 50);
const mapped = rangeSet.map(changes);
// All range positions are adjusted for the insertion

Range Iteration

Advanced iteration patterns for processing range sets.

/**
 * Get an iterator for this range set
 * @param from Starting position (optional)
 */
iter(from?: number): RangeCursor<T>;

/**
 * Range cursor for iterating over ranges
 */
interface RangeCursor<T extends RangeValue> {
  /** Move to the next range */
  next(): void;
  
  /** The current range's start position */
  from: number;
  
  /** The current range's end position */
  to: number;
  
  /** The current range's value */
  value: T;
  
  /** Whether the iterator is at the end */
  done: boolean;
}

Usage Examples:

const rangeSet = RangeSet.of([
  new HighlightValue("yellow").range(5, 10),
  new HighlightValue("blue").range(15, 20),
  new HighlightValue("green").range(25, 30)
]);

// Iterate over all ranges
const cursor = rangeSet.iter();
while (!cursor.done) {
  console.log(`Range: ${cursor.from}-${cursor.to}, color: ${cursor.value.color}`);
  cursor.next();
}

// Iterate starting from a specific position
const partialCursor = rangeSet.iter(12);
while (!partialCursor.done) {
  console.log(`Range from pos 12: ${partialCursor.from}-${partialCursor.to}`);
  partialCursor.next();
}

// Collect ranges in an area
const rangesInArea: Range<HighlightValue>[] = [];
rangeSet.between(8, 22, (from, to, value) => {
  rangesInArea.push(value.range(from, to));
});

Range Set Builder

Efficient builder for constructing large range sets.

/**
 * Builder for efficiently constructing range sets
 */
class RangeSetBuilder<T extends RangeValue> {
  /** Create a new builder */
  constructor();
  
  /** Add a range to the builder */
  add(from: number, to: number, value: T): void;
  
  /** Add a point range to the builder */
  addPoint(pos: number, value: T): void;
  
  /** Finish building and return the range set */
  finish(): RangeSet<T>;
}

Usage Examples:

// Build a large range set efficiently
const builder = new RangeSetBuilder<HighlightValue>();

// Add many ranges (must be added in sorted order)
for (let i = 0; i < 100; i++) {
  const start = i * 10;
  const end = start + 5;
  const color = i % 2 === 0 ? "yellow" : "blue";
  builder.add(start, end, new HighlightValue(color));
}

// Add some point decorations
builder.addPoint(250, new BookmarkValue("bookmark1", "Important"));
builder.addPoint(300, new BookmarkValue("bookmark2", "Note"));

const rangeSet = builder.finish();
console.log(rangeSet.size); // 102 (100 highlights + 2 bookmarks)

Range Comparison

System for comparing range sets and detecting changes.

/**
 * Collection of methods used when comparing range sets
 */
interface RangeComparator<T extends RangeValue> {
  /** Called for ranges that have different values in old vs new sets */
  compareRange(from: number, to: number, activeA: T[], activeB: T[]): void;
  
  /** Called for point ranges that changed */
  comparePoint(from: number, to: number, pointA: T | null, pointB: T | null): void;
  
  /** Called when range boundaries change */
  boundChange?(pos: number): void;
}

/**
 * Compare this range set with another
 * @param other The range set to compare with
 * @param textDiff Changes to map through (optional)
 * @param comparator Object to receive comparison events
 * @param oldLen Length of old document (when using textDiff)
 */
compare(other: RangeSet<T>, textDiff?: ChangeDesc, comparator?: RangeComparator<T>, oldLen?: number): void;

Usage Examples:

const oldRangeSet = RangeSet.of([
  new HighlightValue("yellow").range(5, 10),
  new HighlightValue("blue").range(15, 20)
]);

const newRangeSet = RangeSet.of([
  new HighlightValue("yellow").range(5, 10),    // Same
  new HighlightValue("red").range(15, 20),      // Changed color
  new HighlightValue("green").range(25, 30)     // New
]);

const comparator: RangeComparator<HighlightValue> = {
  compareRange(from, to, activeA, activeB) {
    console.log(`Range ${from}-${to} changed:`);
    console.log("  Old:", activeA.map(v => v.color));
    console.log("  New:", activeB.map(v => v.color));
  },
  
  comparePoint(from, to, pointA, pointB) {
    console.log(`Point at ${from} changed from ${pointA?.color} to ${pointB?.color}`);
  }
};

oldRangeSet.compare(newRangeSet, undefined, comparator);
// Will report the change from blue to red at position 15-20
// and the addition of green at 25-30

Span Iteration

Interface for iterating over spans created by range sets.

/**
 * Methods used when iterating over the spans created by a set of ranges
 */
interface SpanIterator<T extends RangeValue> {
  /** Called for any ranges not covered by point decorations */
  span(from: number, to: number, active: readonly T[], openStart: number): void;
  
  /** Called when going over a point decoration */
  point(from: number, to: number, value: T, active: readonly T[], openStart: number, index: number): void;
}

Types

/**
 * Update specification for range sets
 */
interface RangeSetUpdateSpec<T extends RangeValue> {
  add?: readonly Range<T>[];
  filter?: (from: number, to: number, value: T) => boolean;
  filterFrom?: number;
  filterTo?: number;
}

/**
 * Cursor for iterating over ranges
 */
interface RangeCursor<T extends RangeValue> {
  next(): void;
  from: number;
  to: number;
  value: T;
  done: boolean;
}