CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-codemirror--state

Editor state data structures for the CodeMirror code editor

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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;
}

docs

changes.md

character-utils.md

editor-state.md

extensions.md

index.md

range-sets.md

selection.md

text.md

transactions.md

tile.json