Example: Split View
Though it is possible to create multiple views
from a single editor state, those views will
not, by themselves, stay in sync. States are just immutable values,
and their updated forms will simply diverge.
Thus, to keep the content of two views in sync, you'll have to forward
changes made in one view to the other. A good place to do this is
either an overridden dispatch
function, or an
update listener. In this example,
we'll use the former.
We'll simply use a single start state in this example—though that
isn't necessary, as long as both states have the same document. It
might help, if the document can be big, to give both states the same
Text
instance as starting
document, so that most of the
document tree structure can be shared.
import {basicSetup} from "codemirror"
import {EditorState} from "@codemirror/state"
let startState = EditorState.create({
doc: "The document\nis\nshared",
extensions: basicSetup
})
Next comes the code that will be responsible for broadcasting changes
between the editors. To work around some cyclic reference issues (the
dispatch functions need access to the view, but are passed when
initializing the view), the code stores the views in an array and
refers to them by index.
In order to be able to distinguish between regular transactions caused
by the user and synchronizing transactions from the other editor, we
define an annotation that will be used to tag
such transactions. Whenever a transaction that makes document changes
and isn't a synchronizing transaction comes in, it is also dispatched
to the other editor.
import {EditorView} from "@codemirror/view"
import {Transaction, Annotation} from "@codemirror/state"
let views: EditorView[] = []
let syncAnnotation = Annotation.define<boolean>()
function syncDispatch(from: number, to: number) {
return (tr: Transaction) => {
views[from].update([tr])
if (!tr.changes.empty && !tr.annotation(syncAnnotation))
views[to].dispatch({changes: tr.changes,
annotations: syncAnnotation.of(true)})
}
}
Now we can create the views, and see them in action.
views.push(
new EditorView({
state: startState,
parent: document.querySelector("#editor1"),
dispatch: syncDispatch(0, 1)
}),
new EditorView({
state: startState,
parent: document.querySelector("#editor2"),
dispatch: syncDispatch(1, 0)
})
)
The first editor:
And the second:
Note that non-document state (like selection) isn't shared between the
editors. For most such state, it wouldn't be appropriate to share it.
But there might be cases where additional elements (such as, say,
breakpoint information) needs to be shared. You'll have to set up your
syncing code to forward updates to that shared state (probably as
effects) alongside the document changes.