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

extensions.mddocs/

Extension System

CodeMirror's extension system provides a powerful plugin architecture for adding functionality to editors. ViewPlugins can manage state, handle events, and integrate with the editor's lifecycle.

ViewPlugin Class

class ViewPlugin<T> {
  static define<T>(
    create: (view: EditorView) => T,
    spec?: PluginSpec<T>
  ): ViewPlugin<T>;
  
  static fromClass<T>(
    cls: {new(view: EditorView): T},
    spec?: PluginSpec<T>
  ): ViewPlugin<T>;
}

Plugin Value Interface

interface PluginValue {
  update?(update: ViewUpdate): void;
  destroy?(): void;
}

Plugin Specification

interface PluginSpec<T> {
  eventHandlers?: DOMEventHandlers<T>;
  eventObservers?: DOMEventHandlers<T>;
  provide?: (plugin: ViewPlugin<T>) => Extension;
  decorations?: (value: T) => DecorationSet;
}

ViewUpdate Class

class ViewUpdate {
  readonly view: EditorView;
  readonly state: EditorState;
  readonly transactions: readonly Transaction[];
  readonly changes: ChangeSet;
  readonly startState: EditorState;
  
  // Computed properties
  readonly docChanged: boolean;
  readonly focusChanged: boolean;
  readonly selectionSet: boolean;
  readonly viewportChanged: boolean;
  readonly heightChanged: boolean;
  readonly geometryChanged: boolean;
}

Command Type

type Command = (target: EditorView) => boolean;

Key Extension Facets

const clickAddsSelectionRange: Facet<(event: MouseEvent) => boolean>;
const dragMovesSelection: Facet<(event: MouseEvent) => boolean>;
const mouseSelectionStyle: Facet<MakeSelectionStyle>;
const exceptionSink: Facet<(exception: any) => void>;
const updateListener: Facet<(update: ViewUpdate) => void>;
const inputHandler: Facet<(view: EditorView, from: number, to: number, text: string, insert: () => Transaction) => boolean>;
const focusChangeEffect: Facet<(state: EditorState, focusing: boolean) => StateEffect<any> | null>;
const scrollHandler: Facet<(view: EditorView, range: SelectionRange, options: ScrollOptions) => boolean>;

Extension Utilities

function logException(state: EditorState, exception: any, context?: string): void;

const decorations: Facet<DecorationSet>;
const outerDecorations: Facet<DecorationSet>;
const atomicRanges: Facet<(view: EditorView) => DecorationSet>;
const scrollMargins: Facet<(view: EditorView) => Partial<Rect>>;
const styleModule: Facet<StyleModule>;
const contentAttributes: Facet<AttrSource>;
const editorAttributes: Facet<AttrSource>;
const editable: Facet<boolean>;

Usage Examples

Creating a Simple Plugin

import { ViewPlugin, ViewUpdate, EditorView } from "@codemirror/view";

const myPlugin = ViewPlugin.define((view: EditorView) => {
  console.log("Plugin initialized");
  
  return {
    update(update: ViewUpdate) {
      if (update.docChanged) {
        console.log("Document changed");
      }
      if (update.selectionSet) {
        console.log("Selection changed");
      }
    },
    
    destroy() {
      console.log("Plugin destroyed");
    }
  };
});

// Use the plugin
const view = new EditorView({
  state: EditorState.create({
    extensions: [myPlugin]
  }),
  parent: document.body
});

Plugin with Event Handlers

const eventHandlerPlugin = ViewPlugin.define(() => ({}), {
  eventHandlers: {
    mousedown(event, view) {
      console.log("Mouse down at", event.clientX, event.clientY);
      return false; // Don't prevent default
    },
    
    keydown(event, view) {
      if (event.key === "Tab") {
        // Handle tab key
        view.dispatch(view.state.replaceSelection("  "));
        return true; // Prevent default
      }
      return false;
    }
  }
});

Plugin with Decorations

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

const highlightPlugin = ViewPlugin.define((view) => {
  return {
    decorations: DecorationSet.empty,
    
    update(update: ViewUpdate) {
      if (update.docChanged || update.viewportChanged) {
        this.decorations = this.buildDecorations(update.view);
      }
    },
    
    buildDecorations(view: EditorView) {
      const decorations = [];
      const text = view.state.doc.toString();
      
      // Highlight all instances of "TODO"
      let match;
      const regex = /TODO/g;
      while ((match = regex.exec(text)) !== null) {
        decorations.push(
          Decoration.mark({ class: "todo-highlight" })
            .range(match.index, match.index + match[0].length)
        );
      }
      
      return Decoration.set(decorations);
    }
  };
}, {
  decorations: (value) => value.decorations
});

Class-based Plugin

class StatusBarPlugin implements PluginValue {
  statusBar: HTMLElement;
  
  constructor(view: EditorView) {
    this.statusBar = document.createElement("div");
    this.statusBar.className = "cm-status-bar";
    this.updateStatus(view);
  }
  
  update(update: ViewUpdate) {
    if (update.docChanged || update.selectionSet) {
      this.updateStatus(update.view);
    }
  }
  
  updateStatus(view: EditorView) {
    const selection = view.state.selection.main;
    const line = view.state.doc.lineAt(selection.head);
    this.statusBar.textContent = `Line ${line.number}, Column ${selection.head - line.from + 1}`;
  }
  
  destroy() {
    this.statusBar.remove();
  }
}

const statusBarPlugin = ViewPlugin.fromClass(StatusBarPlugin, {
  provide: (plugin) => showPanel.of(() => ({
    dom: plugin.value?.statusBar || document.createElement("div"),
    top: false
  }))
});

Command Implementation

const insertTimeCommand: Command = (view) => {
  const now = new Date().toLocaleTimeString();
  const selection = view.state.selection.main;
  
  view.dispatch({
    changes: {
      from: selection.from,
      to: selection.to,
      insert: now
    },
    selection: {
      anchor: selection.from + now.length
    }
  });
  
  return true; // Command succeeded
};

// Use with keymap
const timeKeymap = keymap.of([
  { key: "Ctrl-t", run: insertTimeCommand }
]);

Update Listener

const updateLogger = updateListener.of((update: ViewUpdate) => {
  console.log("Update:", {
    docChanged: update.docChanged,
    selectionSet: update.selectionSet,
    focusChanged: update.focusChanged,
    viewportChanged: update.viewportChanged,
    heightChanged: update.heightChanged
  });
  
  if (update.transactions.length > 0) {
    console.log("Transactions:", update.transactions.length);
  }
});

Exception Handling

const errorHandler = exceptionSink.of((exception) => {
  console.error("CodeMirror error:", exception);
  // Could send to error reporting service
});

// Log an exception manually
logException(view.state, new Error("Something went wrong"), "my-plugin");

Input Handler

const customInputHandler = inputHandler.of((view, from, to, text, insert) => {
  // Handle auto-closing brackets
  if (text === "(") {
    view.dispatch({
      changes: [{from, to, insert: "()"}],
      selection: {anchor: from + 1}
    });
    return true; // Handled
  }
  
  return false; // Let default handler run
});

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