Migration Guide
This guide provides rough translations of how various basic operations
in the CodeMirror 5.x interface work with the new system. To do
anything complicated, you'll definitely have to read the
guide, but for quick translations of a few simple method
calls, this document might be enough.
Module System
The 5.x library was distributed as UMD modules, which could either be
loaded directly with <script>
tags or bundled as CommonJS modules.
The new version exposes only modules, which will require some kind of
build step before you can use them.
In addition, the library has been split into a number of separate
packages under the @codemirror
scope. Depending on how closely you
want to configure the editor and how much features you want to load,
you could start with the basic setup,
or hand-pick the core modules you need from the list provided in the
reference manual.
Language support also lives in separate packages. There's a number of
languages available in dedicated packages, with names like
@codemirror/lang-javascript
or @codemirror/lang-rust
.
Many of the CodeMirror 5 modes have been ported to CodeMirror 6's
stream-parser interface and are available
in the
@codemirror/legacy-modes
package.
See the bundling example for a basic example
of how to load the library in the browser.
Creating an Editor
EditorView
from the view package is
roughly the equivalent of the CodeMirror
class from 5.x. This is a
stateful object that manages the editor's DOM representation and basic
user interaction.
import {EditorView} from "@codemirror/view"
let view = new EditorView({parent: document.body})
The parent
option
makes the editor append itself to a given node in the DOM. We'll just
crudely pass document.body
there in these examples, but you'll
usually pass your custom mount point there. It is also possible not to
provide it and, after creating the editor, manually insert
view.dom
somewhere.
The new editor view comes with a bit less built-in behavior than the
old CodeMirror
class, though. By default it doesn't provide things
like key bindings and an undo history.
To add a history and a default set of key
bindings, we must add a few
extensions to the editor. Configuration lives in
the state, so in order to do that we'll have to create an editor
state and provide that to the view.
import {keymap, EditorView} from "@codemirror/view"
import {defaultKeymap, history, historyKeymap} from "@codemirror/commands"
let view = new EditorView({
extensions: [
history(),
keymap.of([...defaultKeymap, ...historyKeymap]),
],
parent: document.body
})
Next, in order to highlight source code, we have to load a language
package and a highlight style, and make sure those are enabled.
import {syntaxHighlighting, defaultHighlightStyle} from "@codemirror/language"
import {javascript} from "@codemirror/lang-javascript"
javascript(),
syntaxHighlighting(defaultHighlightStyle),
This gives you an editor with JavaScript highlighting and indentation.
A line number gutter is also available as an
extension, as are features like rectangular
selection and
highlighting of non-printing
characters.
By default, the view will show the browser's native selection. If you
prefer something closer to the old custom-drawn selection, use the
drawSelection
extension. You can also enable
multiple selections.
Positions
Whereas CodeMirror 5 used {line, ch}
objects to point at document
positions, CodeMirror 6 just uses offsets—the number of characters
(UTF16 code units) from the start of the document, counting line
breaks as one character. This decision was made because manipulating
plain numbers is a lot simpler and more efficient than working with
such objects.
To get information about the line that includes a given position, you
can call .state.doc.
lineAt
(pos)
. The other
way around, you can go from a line number to a line object with the
line
method.
But note: in CodeMirror 6 the first line has number 1, whereas
CodeMirror 5 lines started at 0.
So to convert between old-style positions and offsets, you could use
functions like these:
function posToOffset(doc, pos) {
return doc.line(pos.line + 1).from + pos.ch
}
function offsetToPos(doc, offset) {
let line = doc.lineAt(offset)
return {line: line.number - 1, ch: offset - line.from}
}
For method equivalents shown in this document, assume that positions
used in the new code are always offsets.
Getting the Document and Selection
Your EditorView
object has a
state
property that holds an object
representing its current state. This stores
things like the document,
selection, and configuration.
You can access the current document via
.state.doc
. That holds an
object storing the document as a tree of lines.
cm.getValue() → cm.state.doc.toString()
cm.getRange(a, b) → cm.state.sliceDoc(a, b)
cm.getLine(n) → cm.state.doc.line(n + 1).text
cm.lineCount() → cm.state.doc.lines
The selection, like in the previous
version, consists of a number of selection
ranges, one of which is considered
the main selection.
cm.getCursor() → cm.state.selection.main.head
cm.listSelections() → cm.state.selection.ranges
cm.getSelection() → cm.state.sliceDoc(
cm.state.selection.main.from,
cm.state.selection.main.to)
cm.getSelections() → cm.state.selection.ranges.map(
r => cm.state.sliceDoc(r.from, r.to))
cm.somethingSelected() → cm.state.selection.ranges.some(r => !r.empty)
Making Changes
In the old interface, changes to the editor state were made via direct
method calls to the editor object. In version 6, updates are wrapped
in transactions and then
dispatched to the view up to update it.
This means that different types of update go through a single method,
which takes one or more objects describing the changes, and applies
them atomically. Document changes are described by the
changes
property of the
transaction specs.
cm.replaceRange(text, from, to) → cm.dispatch({
changes: {from, to, insert: text}
})
cm.setValue(text) → cm.dispatch({
changes: {from: 0, to: cm.state.doc.length, insert: text}
})
cm.setState(EditorState.create({doc: text, extensions: ...}))
cm.replaceSelection(text) → cm.dispatch(cm.state.replaceSelection(text))
The selection is updated with the
selection
property.
cm.setCursor(pos) → cm.dispatch({selection: {anchor: pos}})
cm.setSelection(anchor, head) → cm.dispatch({selection: {anchor, head}})
cm.setSelections(ranges) → cm.dispatch({
selection: EditorSelection.create(ranges)
})
cm.extendSelectionsBy(f) ⤳ cm.dispatch({
selection: EditorSelection.create(
cm.state.selection.ranges.map(r => r.extend(f(r))))
})
Situations that would, in the old interface, require the use of
operation
to group updates together tend to not really come up
anymore, since you will just group all your updates into a single
transaction.
When making multiple changes at once
(changes
can also take an array
of change objects), all from
and to
positions refer to the
document at the start of the transaction (as opposed to the document
created by the changes before it). This makes it a lot easier to apply
composite changes.
To create changes based on the selection ranges (along with updated
selection ranges), it is recommended to use the
changeByRange
helper method.
DOM Structure
The new library creates a rather different DOM structure for the
editor. If you're writing custom CSS for the editor, you'll probably
have to change it a bit. Class names roughly correspond like this:
CodeMirror → cm-editor
CodeMirror-line → cm-line
CodeMirror-scroll → cm-scroller
CodeMirror-sizer → cm-content
CodeMirror-focused → cm-focused
CodeMirror-gutters → cm-gutters
CodeMirror-gutter → cm-gutter
CodeMirror-gutter-elt → cm-gutterElement
Highlighting tokens are no longer assigned stable CSS classes. Rather,
a highlight style produces generated
class names for specific syntactic structures. To write or port a
theme, see the One Dark
theme as an example.
cm.focus() → cm.focus()
cm.hasFocus() → cm.hasFocus
cm.getWrapperElement() → cm.dom
cm.getScrollerElement() → cm.scrollDOM
cm.getInputField() → cm.contentDOM
Configuration
Instead of a set of named options, the new configuration system uses a
tree of extension values. Refer to the
guide for a description of this system.
This means that instead of setting an option when creating your
editor, you just dump the extension value that implements the behavior
you want into your set of state
extensions. In many cases (such
as keymaps), the order of the extensions is relevant—those provided
first have a higher precedence than those provided later.
Dynamically changing the configuration is a bit more involved. This
requires you put parts of your configuration in
compartments, and then later dispatch a
transaction that reconfigures that
part.
let tabSize = new Compartment
let view = new EditorView({
extensions: [
tabSize.of(EditorState.tabSize.of(2))
],
})
function setTabSize(size) {
view.dispatch({
effects: tabSize.reconfigure(EditorState.tabSize.of(size))
})
}
Events
CodeMirror 6 no longer uses an event system. The main reason for this
is that that kind of asynchronous notification interface makes it
really hard to implement more complex customizations in a robust way,
and that events tend to be too fine-grained—a given event handler just
tells you about one aspect of what changed, and you need to awkwardly
piece together information from multiple events to get a bigger
picture of what's happening.
Instead, state updates are represented by
transactions, which group all the available
information about the update. If you need to maintain state in sync
with other parts of the editor state, you'll want to use custom state
fields for that. Those are updated for every
transaction using a “reducer”—a function that takes the previous state
and a transaction, and produces a new state.
On the imperative side, updates to the view are represented by
ViewUpdate
objects. It is possible to just
listen for those with an update
listener. But if you need your
response to the update to directly affect the editor somehow, you
might want to define a view plugin instead.
Changing or filtering updates as they happen (similar to the old
"beforeChange"
and "beforeSelectionChange"
events) can be done
with change filters, transaction
filters, or transaction
extenders.
Commands
The concept of "commands" still exists, but there is no longer a
central registry of named commands (getting rid of central registries
was one of the goals of the redesign). Commands are
simply functions that take an editor instance and, if they can,
perform a side effect and return true. The
@codemirror/commands
package exports a number of basic
editing commands (many of which are bound in the default
keymap), and other packages may export
their own relevant commands (see for example undo
and redo
in the history package).
Key bindings are defined as objects, and a keymap
is simply an array of those. Use the keymap
facet
to add keymaps to your configuration. When multiple commands are bound
to a key, they are executed in order of precedence until one of them
returns true.
CodeMirror.fromTextArea
Version 5 has a static fromTextArea
method that tries to
transparently replace a given <textarea>
element with a CodeMirror
instance. Unfortunately, this was never very robust (or even
transparent), so I've decided not to provide this convenience function
in version 6.
Basically, what fromTextArea
did was insert the editor as a sibling
of the textarea, hide the textarea, and, through various hacks, wire
up form submission to sync the content of the editor back to the
textarea (automatically syncing on every change gets too expensive for
big documents).
In its most minimal form, you can do something like this to get
similar behavior:
function editorFromTextArea(textarea, extensions) {
let view = new EditorView({doc: textarea.value, extensions})
textarea.parentNode.insertBefore(view.dom, textarea)
textarea.style.display = "none"
if (textarea.form) textarea.form.addEventListener("submit", () => {
textarea.value = view.state.doc.toString()
})
return view
}
Marked Text
Marked text (and bookmarks) are called
decorations in the new system, and creating them
is a bit more difficult (but also a lot less error-prone).
Instead of adding and removing marks through a side effect,
decorations are provided by extensions, scoped to their source
extension, and only present as long as that extension continues to
provide them.
That means you can't just call a method to mark some text, but have to
define an extension to manage it. Decorations are kept in a range
set, a data structure that associates ranges in
the document with some extra data. The extension must
map these sets to stay in sync with the
document on changes.
This is an example of a simple extension that maintains a set of
decorations.
import {StateField, StateEffect} from "@codemirror/state"
import {EditorView, Decoration} from "@codemirror/view"
const addMarks = StateEffect.define(), filterMarks = StateEffect.define()
const markField = StateField.define({
create() { return Decoration.none },
update(value, tr) {
value = value.map(tr.changes)
for (let effect of tr.effects) {
if (effect.is(addMarks)) value = value.update({add: effect.value, sort: true})
else if (effect.is(filterMarks)) value = value.update({filter: effect.value})
}
return value
},
provide: f => EditorView.decorations.from(f)
})
You could use that extension to mark text like this:
const strikeMark = Decoration.mark({
attributes: {style: "text-decoration: line-through"}
})
view.dispatch({
effects: addMarks.of([strikeMark.range(1, 4)])
})
Or to remove all marks between a
and b
:
function removeMarks(a, b) {
view.dispatch({
effects: filterMarks.of((from, to) => to <= a || from >= b)
})
}
Read the docs for decorations to see how to
collapse parts of the document, insert widgets, or style lines.
In cases where there would be a lot of marks (for example to manage
code highlighting), this approach, which eagerly calculates and
maintains marks for the entire document, may not be ideal. See the
zebra stripes example to learn how to write an
extension that computes decorations only for visible code.