Ok thank you for clarifying. I understand how to listen for changes. I still don’t understand the codemirror modular system. A graphic like this one would help a lot.
I modifed the split view example. I can now keep an arbitrary amount of editors in sync. They can be opened and closed in any order. Awesome. Thank you for that example. It helped me understand that the editor states diverge and are immutable.
I would like to create completely different UIs for various file types.
.bin -> Hex editor
.json -> Json editor
.custom -> Custom editor
It should be possible to have a codemirror and a hex editor of the same file open at the same time.
I currently have the following:
/**
* https://codemirror.net/6/examples/split/
*/
private syncDispatch(fromEditorViewRef: { editorView?: EditorView }) {
return (tr: Transaction): void => {
// this should never happen as the editorref is populated directly after the constructor
if (!fromEditorViewRef.editorView) {
console.log(tr)
throw Error('Trans but editor undefined.')
}
// the EditorView that caused the transaction
const fromEditorView = fromEditorViewRef.editorView
fromEditorView.update([tr])
if (!tr.changes.empty && !tr.annotation(syncAnnotation)) {
// share the transactions with all other editorViews
for (const editorView of this.editorViews)
if (editorView !== fromEditorView) {
editorView.dispatch({
changes: tr.changes,
annotations: syncAnnotation.of(true)
})
}
// I could deliver the changes to my other editors from here but that feels hacky and they would have to register and unregister their listeners
// debounce file changes to avoid saving everytime a key is pressed
if (this.saveTimeout)
clearTimeout(this.saveTimeout)
this.saveTimeout = setTimeout(() => { this.save(fromEditorView) }, this.saveDelay)
}
}
}
/**
* All other editors should also call this function and modify the file through that editor instance.
*/
async createEditor(): Promise<EditorView> {
let editorState: EditorState
if (this.editorViews.length)
// a editor has already been created get use its state as a base
editorState = this.editorViews[0].state
else
// this is the first editor create a new state with the default extensions for a text editor
editorState = await this.initialEditorState()
// As dispatch functions can only be provided to the constructor and the editorView doesn't exist yet a reference object has to be used.
const editorViewRef: {
editorView?: EditorView
} = {}
const newEditorView = new EditorView({
state: editorState,
// create a dispatch function which knowns what editorView is responsible for the transactions.
dispatch: this.syncDispatch(editorViewRef)
})
// set the created editorView so that the dispatch function knowns where the transactions come from.
editorViewRef.editorView = newEditorView
// add the editorView to the list of editorViews to keep in sync by the dispatch function
this.editorViews.push(newEditorView)
return newEditorView
}
The normal TextEditor calls the createEditor
function and attaches it to the dom.
The HexEditor calls the createEditor
function gets a EditorView and can update the other TextEditor instances through the dispatch
function. It could listen to changes via the syncDispatch
function. That however seems like I am doing it wrong because the EditorView would have a nonattached .dom
and update it right? I also can’t use EditorView.updateListener.of(update => ...)
because the base EditorState is already created and extensions can’t be added anymore.
How would you go about creating a completely custom UI that should stay in sync with other editors?