Real-time collaboration plugin for CodeMirror 6. For background & writeup, see Medium: Codemirror 6 Experiments. Overhauled in May 2022 to work with the latest CodeMirror 6 APIs and JSON1. A fully functioning collaborative editor that leverages this library can be found in VZCode.
At its core this library is a translator between Operational Transformation and CodeMirror 6. This is one piece of the puzzle for enabling real-time collaboration on text documents using CodeMirror and ShareDB.
codemirror-ot
provides seamless integration between CodeMirror 6 and Operational Transformation (OT) systems, enabling real-time collaborative editing. The library handles the complex translation between CodeMirror's change representation and OT operations for both JSON0 and JSON1 OT types.
- Bidirectional Translation: Convert between CodeMirror changes and OT operations
- Multiple OT Type Support: Works with both JSON0 and JSON1 operational transformation types
- Unicode Support: Proper handling of Unicode characters including emojis
- Path-based Operations: Support for operations at specific document paths
- ShareDB Integration: Ready-to-use ViewPlugin for ShareDB collaboration
- Hot Module Reloading: Maintains state during development
npm install codemirror-ot
import { EditorView } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { json1Sync } from 'codemirror-ot';
import json1 from 'ot-json1';
import textUnicode from 'ot-text-unicode';
// Assuming you have a ShareDB document
const view = new EditorView({
state: EditorState.create({
doc: shareDBDoc.data.content.files['myfile.js'].text,
extensions: [
json1Sync({
shareDBDoc,
path: ['content', 'files', 'myfile.js', 'text'],
json1,
textUnicode,
debug: false,
}),
],
}),
});
import {
changesToOpJSON0,
changesToOpJSON1,
opToChangesJSON0,
opToChangesJSON1,
} from 'codemirror-ot';
import { EditorState, ChangeSet } from '@codemirror/state';
// Convert CodeMirror changes to OT operations
const state = EditorState.create({ doc: 'Hello World' });
const changes = ChangeSet.of(
[{ from: 5, to: 6, insert: '-' }],
state.doc.length,
);
const json0Op = changesToOpJSON0([], changes, state.doc);
const json1Op = changesToOpJSON1([], changes, state.doc, json1, textUnicode);
// Convert OT operations back to CodeMirror changes
const changesFromJSON0 = opToChangesJSON0(json0Op);
const changesFromJSON1 = opToChangesJSON1(json1Op, 'Hello World');
Converts a CodeMirror ChangeSet to a JSON0 OT operation.
Parameters:
path
-string[]
- Path array for nested document operationschangeSet
-ChangeSet
- CodeMirror ChangeSet containing the changesdoc
-Text
- The original document before changes
Returns: JSON0Op[]
- Array of JSON0 operation components
Example:
const op = changesToOpJSON0(['files', 'index.js'], changeSet, state.doc);
// Result: [{ p: ['files', 'index.js', 5], sd: ' ' }, { p: ['files', 'index.js', 5], si: '-' }]
Converts a CodeMirror ChangeSet to a JSON1 OT operation with proper Unicode handling.
Parameters:
path
-string[]
- Path array for nested document operationschangeSet
-ChangeSet
- CodeMirror ChangeSet containing the changesdoc
-Text
- The original document before changesjson1
-JSON1Type
- JSON1 OT type instancetextUnicode
-TextUnicodeType
- Text-unicode OT type instance
Returns: JSON1Op | null
- JSON1 operation or null for no-ops
Example:
const op = changesToOpJSON1(
['files', 'index.js'],
changeSet,
state.doc,
json1,
textUnicode,
);
// Result: ['files', 'index.js', { es: [5, '-', { d: ' ' }] }]
Converts a JSON0 OT operation to CodeMirror changes.
Parameters:
op
-JSON0Op[]
- Array of JSON0 operation components
Returns: Change[]
- Array of CodeMirror change objects
Example:
const changes = opToChangesJSON0([
{ p: [5], sd: ' ' },
{ p: [5], si: '-' },
]);
// Result: [{ from: 5, to: 6, insert: '-' }]
Converts a JSON1 OT operation to CodeMirror changes with Unicode position conversion.
Parameters:
op
-JSON1Op
- JSON1 operationoriginalDoc
-string
(optional) - Original document for Unicode position conversion
Returns: Change[]
- Array of CodeMirror change objects
Example:
const changes = opToChangesJSON1([{ es: [5, '-', { d: ' ' }] }], 'Hello World');
// Result: [{ from: 5, to: 6, insert: '-' }]
Creates a CodeMirror ViewPlugin that synchronizes with ShareDB using JSON1 operations.
Parameters:
options
-Object
shareDBDoc
- ShareDB document instancepath
-string[]
(default: []) - Path to the text content in the documentjson1
- JSON1 OT type instancetextUnicode
- Text-unicode OT type instancedebug
-boolean
(default: false) - Enable debug logging
Returns: ViewPlugin
- CodeMirror ViewPlugin for real-time collaboration
Example:
const syncPlugin = json1Sync({
shareDBDoc: myShareDBDoc,
path: ['content', 'files', 'main.js', 'text'],
json1,
textUnicode,
debug: true,
});
Features:
- Bidirectional Sync: Automatically syncs changes between CodeMirror and ShareDB
- Multi-part Operations: Handles complex operations with multiple components
- Path Filtering: Only processes operations that affect the specified path
- Lock Mechanism: Prevents infinite loops during synchronization
- Hot Module Reloading: Maintains editor state during development updates
Determines if an OT operation can affect content at the specified path.
Parameters:
op
-JSON1Op | null
- The operation to checkpath
-string[]
- The path to check against
Returns: boolean
- True if the operation affects the path
Example:
const canAffect = canOpAffectPath(
['content', 'files', 'main.js', 'text', { es: [5, 'hello'] }],
['content', 'files', 'main.js', 'text'],
);
// Result: true
The library properly handles Unicode characters, including emojis and multi-byte characters, by converting between UTF-16 positions (used by CodeMirror) and Unicode code point positions (used by text-unicode OT type).
// Unicode emoji handling
const changes = opToChangesJSON1(
[{ es: [2, 'World', { d: 'Hello' }] }],
'π Hello',
);
// Correctly handles emoji position conversion
The library includes robust error handling for:
- Null or undefined operations
- Invalid path structures
- Unicode conversion edge cases
- Malformed OT operations
Run the test suite:
npm test
The test suite includes comprehensive coverage of:
- String insertions, deletions, and replacements
- Unicode character handling
- Path-based operations
- Multi-part operations
- Real-world collaboration scenarios
- CodeMirror 6 - The text editor this library integrates with
- ShareDB - Real-time database with OT support
- JSON1 - JSON operational transformation type
- VZCode - Collaborative code editor using this library
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Add tests for your changes
- Ensure all tests pass (
npm test
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.