sclass EREvent { } sclass ERText extends EREvent { int startIndex, endIndex; S text; } sclass ERCaret extends EREvent { int pos; } sclass EditRecorder { VF1 onEvent; *() {} *(VF1 *onEvent) {} // editor state S text = ""; int caret; // update the text void update(S newText) { text = unnull(text); newText = unnull(newText); S a = text, b = newText; int i = lCommonPrefix(a, b); a = substring(a, i); b = substring(b, i); int j = lCommonSuffix(a, b); a = substring(a, 0, l(a)-j); b = substring(b, 0, l(b)-j); // Now differing part remains in a and b; // i and j give length of unchanged beginning+end if (empty(a) && empty(b)) ret; ERText change = nu(ERText, startIndex := i, endIndex := i+l(a), text := b); // Verify the change we made assertEquals(newText, replayTextEdit(change, text)); text = newText; onEvent(change); } void updateCaret(int pos) { if (pos != caret) { onEvent(nu(ERCaret, +pos)); caret = pos; } } void onEvent(EREvent e) { callF(onEvent, e); } static S replayTextEdit(ERText change, S text) { if (change == null) ret text; ret substring(text, 0, change.startIndex) + change.text + substring(text, change.endIndex); } }