CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-codemirror--view

DOM view component 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

decorations.mddocs/

Decorations

The decoration system provides flexible styling and widget insertion capabilities for CodeMirror editors. Decorations can mark text ranges, insert widgets, replace content, and style entire lines.

Decoration Class

abstract class Decoration {
  static mark(spec: MarkDecorationSpec): Decoration;
  static widget(spec: WidgetDecorationSpec): Decoration;
  static replace(spec: ReplaceDecorationSpec): Decoration;
  static line(spec: LineDecorationSpec): Decoration;
  
  readonly spec: any;
  readonly startSide: number;
  readonly endSide: number;
  readonly point: boolean;
  
  range(from: number, to?: number): Range<Decoration>;
}

DecorationSet Class

class DecorationSet extends RangeSet<Decoration> {
  static readonly empty: DecorationSet;
  static create(view: EditorView, decorations: Range<Decoration>[] | readonly Range<Decoration>[]): DecorationSet;
  
  update(mapping: ChangeDesc, filterFrom?: number, filterTo?: number): DecorationSet;
  map(mapping: ChangeDesc, start?: number, end?: number): DecorationSet;
}

Mark Decoration Specification

interface MarkDecorationSpec {
  inclusive?: boolean;
  inclusiveStart?: boolean;
  inclusiveEnd?: boolean;
  attributes?: {[key: string]: string};
  class?: string;
  tagName?: string;
  bidiIsolate?: Direction | null;
  [other: string]: any;
}

Widget Decoration Specification

interface WidgetDecorationSpec {
  widget: WidgetType;
  side?: number;
  block?: boolean;
  inlineOrder?: boolean;
  [other: string]: any;
}

WidgetType Base Class

abstract class WidgetType {
  abstract toDOM(view: EditorView): HTMLElement;
  abstract eq(other: WidgetType): boolean;
  
  updateDOM?(dom: HTMLElement, view: EditorView): boolean;
  get estimatedHeight(): number;
  ignoreEvent?(event: Event): boolean;
  coordsAt?(dom: HTMLElement, pos: number, side: number): Rect | null;
  get isHidden(): boolean;
  destroy?(dom: HTMLElement): void;
}

Replace Decoration Specification

interface ReplaceDecorationSpec {
  widget?: WidgetType;
  inclusive?: boolean;
  inclusiveStart?: boolean;
  inclusiveEnd?: boolean;
  block?: boolean;
  [other: string]: any;
}

Line Decoration Specification

interface LineDecorationSpec {
  attributes?: {[key: string]: string};
  class?: string;
  [other: string]: any;
}

BlockType Base Class

abstract class BlockType {
  abstract toDOM(view: EditorView, pos: number): HTMLElement;
  abstract updateDOM(dom: HTMLElement, view: EditorView, pos: number): boolean;
  get side(): number;
  get editable(): boolean;
  get estimatedHeight(): number;
  get lineBreaks(): number;
}

Usage Examples

Text Highlighting

import { Decoration, DecorationSet, ViewPlugin } from "@codemirror/view";

// Highlight all instances of a word
const highlightWord = (word: string, className: string) => {
  return ViewPlugin.define((view) => {
    return {
      decorations: buildHighlights(view, word, className),
      
      update(update) {
        if (update.docChanged || update.viewportChanged) {
          this.decorations = buildHighlights(update.view, word, className);
        }
      }
    };
  }, {
    decorations: v => v.decorations
  });
};

function buildHighlights(view: EditorView, word: string, className: string) {
  const decorations = [];
  const regex = new RegExp(word, 'gi');
  const text = view.state.doc.toString();
  
  let match;
  while ((match = regex.exec(text)) !== null) {
    decorations.push(
      Decoration.mark({ class: className })
        .range(match.index, match.index + match[0].length)
    );
  }
  
  return Decoration.set(decorations);
}

Line Highlighting

// Highlight error lines
const errorLines = [5, 12, 18]; // Line numbers with errors

const errorDecorations = errorLines.map(lineNumber => {
  const line = view.state.doc.line(lineNumber);
  return Decoration.line({ class: "error-line" }).range(line.from);
});

const errorHighlights = Decoration.set(errorDecorations);

Widget Decorations

class LineWidget extends WidgetType {
  constructor(private message: string) {
    super();
  }
  
  toDOM(view: EditorView): HTMLElement {
    const div = document.createElement("div");
    div.className = "line-widget";
    div.textContent = this.message;
    return div;
  }
  
  eq(other: WidgetType): boolean {
    return other instanceof LineWidget && other.message === this.message;
  }
  
  get estimatedHeight() {
    return 20; // Height in pixels
  }
}

// Add widget after a line
const line = view.state.doc.line(3);
const widget = Decoration.widget({
  widget: new LineWidget("This is a line widget"),
  side: 1,
  block: true
}).range(line.to);

Inline Widgets

class InlineButton extends WidgetType {
  constructor(private text: string, private onClick: () => void) {
    super();
  }
  
  toDOM(view: EditorView): HTMLElement {
    const button = document.createElement("button");
    button.textContent = this.text;
    button.onclick = this.onClick;
    return button;
  }
  
  eq(other: WidgetType): boolean {
    return other instanceof InlineButton && other.text === this.text;
  }
  
  ignoreEvent(event: Event): boolean {
    return event.type !== "mousedown";
  }
}

// Insert inline widget at cursor
const pos = view.state.selection.main.head;
const inlineWidget = Decoration.widget({
  widget: new InlineButton("Click me", () => alert("Clicked!")),
  side: 0
}).range(pos);

Replace Decorations

class FoldWidget extends WidgetType {
  constructor(private foldedText: string) {
    super();
  }
  
  toDOM(view: EditorView): HTMLElement {
    const span = document.createElement("span");
    span.className = "cm-fold-placeholder";
    span.textContent = `[${this.foldedText}]`;
    span.title = "Click to unfold";
    return span;
  }
  
  eq(other: WidgetType): boolean {
    return other instanceof FoldWidget && other.foldedText === this.foldedText;
  }
}

// Replace text range with fold widget
const foldDecoration = Decoration.replace({
  widget: new FoldWidget("..."),
  inclusive: true
}).range(from, to);

Mark Decorations with Attributes

// Add custom attributes to marked text
const linkDecoration = Decoration.mark({
  tagName: "a",
  attributes: {
    href: "https://example.com",
    target: "_blank",
    title: "External link"
  },
  class: "external-link"
});

// Apply to a range
const decoratedRange = linkDecoration.range(from, to);

Bidirectional Text Isolation

// Isolate RTL text in LTR context
const rtlDecoration = Decoration.mark({
  bidiIsolate: Direction.RTL,
  class: "rtl-text"
}).range(from, to);

Dynamic Decoration Updates

const dynamicDecorations = ViewPlugin.define((view) => {
  let decorations = DecorationSet.empty;
  
  return {
    decorations,
    
    update(update) {
      if (update.docChanged) {
        // Map existing decorations through changes
        decorations = decorations.map(update.changes);
        
        // Add new decorations based on content
        const newDecos = findAndDecorate(update.view);
        decorations = decorations.update({
          add: newDecos,
          filter: (from, to) => {
            // Remove decorations that are no longer valid
            return isStillValid(from, to, update.view);
          }
        });
      }
    }
  };
}, {
  decorations: v => v.decorations
});

Conditional Decoration Styling

// Different styles based on context
const contextualMark = (type: "warning" | "error" | "info") => {
  const classMap = {
    warning: "text-warning",
    error: "text-error", 
    info: "text-info"
  };
  
  return Decoration.mark({
    class: classMap[type],
    attributes: { "data-severity": type }
  });
};

// Apply different decorations
const warningRange = contextualMark("warning").range(10, 20);
const errorRange = contextualMark("error").range(30, 40);

Performance Considerations

// Efficient decoration building for large documents
function buildDecorationsEfficiently(view: EditorView) {
  const decorations = [];
  const { from, to } = view.viewport;
  
  // Only process visible range
  const visibleText = view.state.sliceDoc(from, to);
  let match;
  const regex = /pattern/g;
  
  while ((match = regex.exec(visibleText)) !== null) {
    const start = from + match.index;
    const end = start + match[0].length;
    
    decorations.push(
      Decoration.mark({ class: "highlight" }).range(start, end)
    );
  }
  
  return Decoration.set(decorations);
}

MatchDecorator

Automatic pattern matching and decoration system for finding and styling regular expression matches.

class MatchDecorator {
  constructor(config: MatchDecoratorConfig);
  createDeco(view: EditorView): DecorationSet;
}

interface MatchDecoratorConfig {
  regexp: RegExp;
  decoration?: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration);
  decorate?: (add: (from: number, to: number, decoration: Decoration) => void, from: number, to: number, match: RegExpExecArray, view: EditorView) => void;
  boundary?: RegExp;
}

MatchDecorator Usage

import { MatchDecorator, Decoration, ViewPlugin } from "@codemirror/view";

// Highlight URLs automatically
const urlMatcher = new MatchDecorator({
  regexp: /https?:\/\/[^\s]+/g,
  decoration: Decoration.mark({
    class: "url-link",
    tagName: "a",
    attributes: {
      href: match => match[0],
      target: "_blank"
    }
  })
});

const urlPlugin = ViewPlugin.define((view) => ({
  decorations: urlMatcher.createDeco(view),
  update(update) {
    if (update.docChanged || update.viewportChanged) {
      this.decorations = urlMatcher.createDeco(update.view);
    }
  }
}), {
  decorations: v => v.decorations
});

// Dynamic decoration based on match content
const todoMatcher = new MatchDecorator({
  regexp: /TODO:\s*(.+)/g,
  decorate: (add, from, to, match, view) => {
    const priority = match[1].includes("URGENT") ? "high" : "normal";
    add(from, to, Decoration.mark({
      class: `todo-item priority-${priority}`,
      attributes: { "data-todo": match[1] }
    }));
  }
});

docs

bidi.md

builtin-extensions.md

decorations.md

editor-view.md

extensions.md

gutters.md

index.md

keybindings.md

layout.md

panels.md

tooltips.md

tile.json