Undo and cursor position

I load a checkpoint (doc + steps from it) into the state and history like this:

let state=EditorState.create({ schema, doc: doc, plugins: view.getState().plugins });
for (let i=0; i<steps.length; i++) {
	let tr=state.tr.step(pmJSON.from(ver.steps[i]));
	closeHistory(tr);
	state = state.apply(tr);
}

view.updateView(state);

the problem I have is that when pressing undo, the view changes correctly but the cursor jumps to the beginning of the document.

How can I get the cursor to move to the position of the step that was ‘undoed’ ?

Thx!

The undo history will store the current selection in the history. Since the selection was at the default position (start of doc) when you fed it the changes, that’s where it’ll put it when you undo. You can use setSelection during replaying to work around this.

I am not sure how to use setSelection here :frowning:

given a transaction, how do I change it’s position to another ? I looked at the doc and other discussions here but could not find a clear answer, any help appreciated.

thx!

I tried

let state=EditorState.create({ schema, doc: doc, plugins: view.getState().plugins });
for (let i=0; i<steps.length; i++) {
	let tr=state.tr.step(steps[i]));
        tr.setSelection(Selection.near(state.doc.resolve(step.to)));
	closeHistory(tr);
	state = state.apply(tr);
}

view.updateView(state);

but I get very strange results, if for example I load steps which just add to the end, undoing them seems to skip to one char before the end.

I also tried: tr.setSelection(Selection.near(state.doc.resolve(step.getMap().map(step.to, 1))));

but got the same result.

Is there a correct way of doing this ?

this seems to work:

let state=EditorState.create({ schema, doc: doc, plugins: view.getState().plugins });
for (let i=0; i<steps.length; i++) {
	let step=steps[i];
    let tr=state.tr.step(step));
	closeHistory(tr);
	state = state.apply(tr);
    let endPos=Selection.near(state.doc.resolve(step.getMap().map(step.to, 1)));
	state = state.apply(state.tr.setSelection(endPos));
}

view.updateView(state);

but seems a bit convulted, is that correct ? is there a simpler way ?

You can’t rely on steps having from or to properties (even for replace steps, those properties aren’t part of the public API). One way to get information about the extent of a step is to use its position map (see its forEach method). I assume you’re trying to get the position in the old doc, before the step? But if so, I’m not sure why you are applying the step’s map – or are these inverted steps?

They are not inverted.

So how do I move the cursor to the end position of the result of applying the step ?

Any help ?

to put it more succinctly, how can I run state.tr.step(step) and then move the cursor to the position it would be if I entered that step manually ?

perhaps a flag to Transfrorm.step can be added ?

There is no single cursor position that can be derived from a step. As I said, you can use step.getMap.forEach to find out which parts of the document it touched.

Certainly not. The transform module doesn’t even know selections exist, and that’s how it should be.

There is no single cursor position that can be derived from a step

I’m confused, doesn’t the editor calculate a new cursor position when the user manually creates the edit that the step is derived from ?

The transform module doesn’t even know selections exist

got it thx

.

I played with this a lot and still can’t get it to work properly :frowning: :frowning: I tried:

let state=EditorState.create({ schema, doc: doc, plugins: view.getState().plugins });

for (let i=0; i<steps.length; i++) {
	let step=steps[i];
	state = state.apply(state.tr.step(step));

	let pos=-1;
	step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
		if (newEnd>pos) pos=newEnd;
	});
	console.log(pos);

	tr=state.tr.setSelection(Selection.near(state.doc.resolve(pos)));
	closeHistory(tr);
	state = state.apply(tr);
}

view.updateView(state);

I now feed an empty doc, and a steps array created by typing 1 then 2 then {home} then x

the console shows 2, 3, 2 which is correct

but on pressing undo the cursor stays at the end - it does not jump to position 2

if I keep pressing undo further, the cursor moves correctly so the problem seems to be with the last setSelection not being remembered by history.

Any help ?

Sure, and it does so when you apply the step programatically too, but it computes it from the old selection, so that doesn’t give you the selection you’re looking for (the cursor will probalby simply stay at the start of the document).

Your code is using a post-step position and applying it before the step, which is probably not what you mean and might produce an error if the position is out of range. You might get better results using oldEnd.

ok, so if that is so, given a start position and a step what is the correct way to calculate the cursor position after applying the step ? is that the maximum of all newEnd in all changed ranges ?

I ended up with code that works, though I’m not sure is correct: (I didn’t close the history properly above)

let state=EditorState.create({ schema, doc: doc, plugins: view.getState().plugins });

for (let i=0; i<steps.length; i++) {
	let step=steps[i];

	let tr=state.tr.step(step);
	state = state.apply(tr);

	let pos=-1;
	step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
		if (newEnd>pos) pos=newEnd;
	});
	tr=state.tr.setSelection(Selection.near(state.doc.resolve(pos)));
	state = state.apply(tr);

	state= state.apply(closeHistory(state));
}

view.updateView(state);

Again, there is no ‘cursor position after applying the step’. If you want to map a given previous selection through a step, you use the selection’s map method. But it sounds like that’s not what you are talking about.

You could put the .step and .setSelection in the same transaction to save some transactions, but otherwise that looks sane.

No I can’t because I need state.doc passed to setSelection already updated with the step

It doesn’t seem to work otherwise, unless I’m missing something ?

You can pass tr.doc