Question

Using Code Mirror, I need to map change event data to a list of tuples containing:

(Text before change, Change text, Text after change)

I am currently listening on the change event and can handle input changes, but have to code specific solutions for deletions, selection movements, and undos.

Is there a more reliable approach that works with the standard events?

Edit

Adding current work.

Thinking a bit more, I only care about the first (in terms of position) change in a document. Here is what I currently do:

var docStart = {'line': 0, 'ch': 0},
    docEnd = {'line': Infinity, 'ch': Infinity};

// Just assume that we always have a single change and it is first
// for this example.
cm.on('change', function(cm, change) {        
    var start, end, text; switch (change.origin) {
    case '+delete':
        start = change.from;
        end = change.from;
        text = '';
        break;
    case 'undo':
        start = change.from;
        end = change.from;
        text = change.text.join('\n');
        break;
    case 'redo':
        start = change.from;
        end = {'line': change.to.line, 'ch': change.to.ch + 1};
        text = '';
        break;
    default:
        start = change.from;
        end = {'line': change.to.line, 'ch': change.to.ch + 1};
        text = change.text.join('\n');
        break;
    }

    var pre = cm.doc.getRange(docStart, start);
    var post = cm.doc.getRange(end, docEnd);

    [pre, text, post]; // output
};

This is not correct. Not all event types are handled and many cases like line terminators also are not handled correctly or constantly. An alternative would be greatly appreciated.

Was it helpful?

Solution

To get text's before edit, you could use beforeChange event. Than the text fetching is pretty straightforward. beforeChange also has nice property, as it's fired for each change (no chaining as with change).

cm.on("beforeChange", function (cm, change) {
  var before = cm.getRange({ line: 0, ch: 0 }, change.from);
  var text = cm.getRange(change.from, change.to);
  var after = cm.getRange(change.to, { line: cm.lineCount() + 1, ch: 0 });
  console.log("before change", before, text, after);
});

If you need the text after change, than you have to calculate to in post-change coordinate system. Luckily it's quite easy still:

cm.on("change", function (cm, change) {
  var from = change.from;
  var text = change.text.join("\n");
  var removed = change.removed.join("\n");
  var to =  cm.posFromIndex(cm.indexFromPos(from) + text.length);

  var before = cm.getRange({ line: 0, ch: 0 }, from);
  var after = cm.getRange(to, { line: cm.lineCount() + 1, ch: 0 });

  console.log("after change", before, removed, text, after);
});

Hopes this works, I hadn't time to test it properly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top