I believe there would be some shorter solution, but if not, here is working one if someone would need.
import { json } from ‘@codemirror/lang-json’;
import { MergeView } from ‘@codemirror/merge’;
import { EditorView, basicSetup } from ‘codemirror’;
import { foldEffect, foldNodeProp, foldService, foldState, syntaxHighlighting, syntaxTree, unfoldEffect } from ‘@codemirror/language’;
import { EditorState, Extension, SelectionRange, StateEffect, StateEffectType, StateField, Transaction } from ‘@codemirror/state’;//Remove what is not necessary
transactionHandler =
EditorState.transactionFilter.of(tr => {
//this part is responsible to listen to fold/unfold events on one of the editors.
for (const effect of tr.effects) {
if (effect.is(foldEffect) || effect.is(unfoldEffect)) { //Check if transaction is one we need
const otherEditorView = editor.a.state === tr.startState ? editor.b : editor.a;
setTimeout(() => {//without timeout there are some errors happening. Seems we need to wait
//initial transaction to finish
this.foldUnfoldPairEditor(tr, effect, otherEditorView);
}, 0);
}
}
return [tr];
});
editor: MergeView = new MergeView({
a: {
doc: json1,//change to your json
extensions: [basicSetup, json(), this.transactionHandler]
},
b: {
doc: json2,//change to your json
extensions: [basicSetup, json(), this.transactionHandler]
},
parent: element//container Element
});
foldUnfoldPairEditor(tr: Transaction, effect: StateEffect, otherEditorView: EditorView): void {
if (this.foldInProgress) {
//We need this to avoid recursive calles. Skipping second call from pair editor.
this.foldInProgress = false;
} else {
this.foldInProgress = true;
if (effect.is(foldEffect) || effect.is(unfoldEffect)) {
//finally you need position, but working with lines is easier, so example is with lines.
const currentLine = tr.startState.doc.lineAt(effect.value.from);
const otherEditorLine = this.getMatchingLine(currentLine.number);//Your match funciton
if (otherEditorLine != -1) {
const otherEditorBlock = otherEditorView.lineBlockAt(otherEditorView.state.doc.line(otherEditorLine).from);
if (effect.is(foldEffect)) {
//For some reason foldable funciton is not exported from language module, so duplicating it here, and adding necessary dependancies.
let range = this.foldable(otherEditorView.state, otherEditorBlock.from, otherEditorBlock.to);
otherEditorView.dispatch({ effects: foldEffect.of(range) });//Action for pair editor
} else {
// Same for findFold funciton.
let folded = this.findFold(otherEditorView.state, otherEditorBlock.from, otherEditorBlock.to);
otherEditorView.dispatch({ effects: unfoldEffect.of(folded) });//Action for pair editor
}
} else {
this.foldInProgress = false;
}
}
}
}
//Duplicate from language module
findFold(state, from, to) {
var _a;
let found = null;
(_a = state.field(foldState, false)) === null || _a === void 0 ? void 0 : _a.between(from, to, (from, to) => {
if (!found || found.from > from)
found = { from, to };
});
return found;
}
//Duplicate from language module
foldable(state, lineStart, lineEnd) {
for (let service of state.facet(foldService)) {
let result = service(state, lineStart, lineEnd);
if (result)
return result;
}
return this.syntaxFolding(state, lineStart, lineEnd);
}
//Duplicate from language module
syntaxFolding(state, start, end) {
let tree = syntaxTree(state);
if (tree.length < end)
return null;
let stack = tree.resolveStack(end, 1);
let found = null;
for (let iter = stack; iter; iter = iter.next) {
let cur = iter.node;
if (cur.to <= end || cur.from > end)
continue;
if (found && cur.from < start)
break;
let prop = cur.type.prop(foldNodeProp);
if (prop && (cur.to < tree.length - 50 || tree.length == state.doc.length || !this.isUnfinished(cur))) {
let value = prop(cur, state);
if (value && value.from <= end && value.from >= start && value.to > end)
found = value;
}
}
return found;
}
//Duplicate from language module
isUnfinished(node) {
let ch = node.lastChild;
return ch && ch.to == node.to && ch.type.isError;
}
getMatchingLine(line: number): number {
return line;
}