Sign in

@liveblocks/codemirror

@liveblocks/codemirror provides CodeMirror 6 plugins that sync a document with LiveText in Storage and display remote carets and selections. Read our React or Next.js get started guides to learn more.

Setup

Install Liveblocks, CodeMirror, and this package:

Terminal
npm install @liveblocks/client @liveblocks/react @liveblocks/codemirror codemirror

Each Liveblocks package should use the same version.

Create a room with a LiveText document in Storage and an initial presence shape for selection cursors:

liveblocks.config.ts
import type { LiveblocksCodemirrorSelection } from "@liveblocks/codemirror";import { LiveText } from "@liveblocks/client";
declare global { interface Liveblocks { Presence: { selection: LiveblocksCodemirrorSelection | null; }; Storage: { document: LiveText; }; UserMeta: { id?: string; info?: { name?: string; color?: string; }; }; }}
export {};
App.tsx
"use client";
import { LiveText } from "@liveblocks/client";import { ClientSideSuspense, LiveblocksProvider, RoomProvider,} from "@liveblocks/react/suspense";import { Editor } from "./Editor";
export default function App() { return ( <LiveblocksProvider publicApiKey={""}> <RoomProvider id="my-room" initialPresence={{ selection: null }} initialStorage={{ document: new LiveText("Hello, world") }} > <ClientSideSuspense fallback={<div>Loading…</div>}> <Editor /> </ClientSideSuspense> </RoomProvider> </LiveblocksProvider> );}

Attach the plugins after Storage has loaded. Create the editor with the LiveText content and both plugins in the initial extensions:

Editor.tsx
"use client";
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";import { EditorView } from "@codemirror/view";import { EditorState } from "@codemirror/state";import type { LiveText, Room } from "@liveblocks/client";import { createLiveblocksPresencePlugin, createLiveblocksSyncPlugin,} from "@liveblocks/codemirror";import { useRoom } from "@liveblocks/react/suspense";
export function Editor() { const room = useRoom(); const root = useRoot(room);
if (root == null) { return <div>Loading…</div>; }
return <EditorInner text={root.get("document")} />;}
function EditorInner({ text }: { text: LiveText }) { const room = useRoom(); const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => { if (containerRef.current === null) return;
const view = new EditorView({ parent: containerRef.current, state: EditorState.create({ doc: text.toString(), extensions: [ createLiveblocksSyncPlugin(room, text), createLiveblocksPresencePlugin(room, text), ], }), });
return () => { view.destroy(); }; }, [room, text]);
return <div ref={containerRef} className="editor" />;}
function useRoot(room: Room) { const subscribe = room.events.storageDidLoad.subscribeOnce; const getSnapshot = room.getStorageOrNull; const getServerSnapshot = useCallback(() => null, []); return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);}

Add styles for remote carets and selections. This package does not ship a stylesheet.

globals.css
.lb-remote-selection {  position: absolute;  background-color: color-mix(in srgb, var(--lb-remote-color) 25%, transparent);  border-radius: 1px;  pointer-events: none;  box-sizing: border-box;}
.lb-remote-caret { position: absolute; width: 0; border-left: 2px solid var(--lb-remote-color); pointer-events: none; box-sizing: border-box;}

createLiveblocksSyncPlugin

Keeps a CodeMirror document in sync with a LiveText node in Storage. Local edits are written to Storage. Remote edits are applied to the editor. The plugin also wires undo and redo to the room’s history:

  • Mod-z — undo
  • Mod-y — redo
  • Shift-Mod-z — redo on macOS
import { createLiveblocksSyncPlugin } from "@liveblocks/codemirror";
const sync = createLiveblocksSyncPlugin(room, text);
Returns
  • extensionExtension

    A CodeMirror extension to add to your editor state.

Arguments

createLiveblocksPresencePlugin

Broadcasts the local selection to other clients and renders remote carets and selection highlights. Remote caret colors come from each user’s user.info.color. Set user info when authenticating or joining a room.

The plugin renders elements with the .lb-remote-caret and .lb-remote-selection class names. Style them in your app CSS using the --lb-remote-color CSS variable.

import { createLiveblocksPresencePlugin } from "@liveblocks/codemirror";
const presence = createLiveblocksPresencePlugin(room, text);

Add the returned extensions to your editor:

extensions: [  createLiveblocksSyncPlugin(room, text),  createLiveblocksPresencePlugin(room, text),];
Returns
  • extensionsExtension[]

    CodeMirror extensions that track remote selections and render carets.

Arguments

LiveblocksCodemirrorSelection

The presence selection shape used by createLiveblocksPresencePlugin. Positions are encoded against the LiveText version so remote carets stay stable across concurrent edits.

import type { LiveblocksCodemirrorSelection } from "@liveblocks/codemirror";
type Presence = { selection: LiveblocksCodemirrorSelection | null;};
Properties
  • anchornumberRequired

    Encoded selection anchor index.

  • headnumberRequired

    Encoded selection head index.

  • versionnumberRequired

    The LiveText version used when encoding anchor and head.

Typing

Type your room’s presence, Storage, and user metadata in liveblocks.config.ts. Use LiveblocksCodemirrorSelection for the presence selection field.

liveblocks.config.ts
import type { LiveblocksCodemirrorSelection } from "@liveblocks/codemirror";import { LiveText } from "@liveblocks/client";
declare global { interface Liveblocks { Presence: { selection: LiveblocksCodemirrorSelection | null; }; Storage: { document: LiveText; }; UserMeta: { id?: string; info?: { name?: string; color?: string; }; }; }}
export {};

When joining a room, set initialPresence to { selection: null } and initialStorage to your LiveText document:

<RoomProvider  id="my-room"  initialPresence={{ selection: null }}  initialStorage={{ document: new LiveText("Hello, world") }}>  {/* children */}</RoomProvider>