In the docs on atomicRanges there is facet extension used to skip atoms. I was wondering, is it possible to detect the cursor movement into the replacement atomic decoration and send an event to the corresponding WidgetType?
I guess I need to apply a custom transaction filter to stop the cursor movement and fire an event on a widget. So I check the source code of atomicRanges to have an idea how to do it
function skipAtoms(view, oldPos, pos) {
let atoms = view.state.facet(atomicRanges).map(f => f(view));
for (;;) {
let moved = false;
for (let set of atoms) {
set.between(pos.from - 1, pos.from + 1, (from, to, value) => {
if (pos.from > from && pos.from < to) {
pos = oldPos.head > pos.from ? EditorSelection.cursor(from, 1) : EditorSelection.cursor(to, -1);
moved = true;
}
});
}
if (!moved)
return pos;
}
}
It looks like that an atom object contains only ranges of symbols, which cover atoms, but they have no direct references to the instances of a WidgetType placed on the corresponding range. Or where should I look for that?
No, atomicRanges can’t do that, but instead of using that you could define a transaction filter that, when the selection ends up inside one of your ranges, moves it as appropriate and adds some effect that makes your extension update the appropriate widget (widgets are stateless, so you don’t really send events to them, but rather you replace them with a different instance).
I tried transactionFilter it works by its own flawlessly. However, I don’t know how to prioritize it over atomicRange features.
I managed to detect the cursor going into an atomic range decoration, but it can only detect 1 character before/after the actual chunk. It seems atomic range has a priority in transactions and alters the transaction before my filter, which leads to a jump over the widget position and then my filter is useless.
I found in source code, that function skipAtoms already checks chunks
function skipAtoms(view, oldPos, pos) {
let atoms = view.state.facet(atomicRanges).map(f => f(view));
for (;;) {
let moved = false;
for (let set of atoms) {
set.between(pos.from - 1, pos.from + 1, (from, to, value) => {
if (pos.from > from && pos.from < to) {
pos = oldPos.head > pos.from ? EditorSelection.cursor(from, 1) : EditorSelection.cursor(to, -1);
moved = true;
console.warn(value.widget); // HERE
}
});
}
if (!moved)
return pos;
}
}
It would be great to have somewhat a callback function from this or that atomicRange instance, which by the default returns true and allows alter the transaction. Then, anyone could define such function for a widget and avoid extra computations when checking borders in transaction filters (second time).
In the given example an access to a widget is possible via value.widget
UPD: Something like this could extend the features of atomicRange
function skipAtoms(view, oldPos, pos) {
let atoms = view.state.facet(atomicRanges).map(f => f(view));
for (;;) {
let moved = false;
for (let set of atoms) {
set.between(pos.from - 1, pos.from + 1, (from, to, value) => {
if (pos.from > from && pos.from < to) {
pos = oldPos.head > pos.from ? EditorSelection.cursor(from, 1) : EditorSelection.cursor(to, -1);
moved = true;
if (value.widget.skipPosition) { //HERE
pos = value.widget.skipPosition(pos, oldPos)
}
}
});
}
if (!moved)
return pos;
}
}
After two days, I have realized, I probably need to implement a custom version of atomicRanges where one could control this behavior, then there is no need in making more complicated version of atomic ranges.
Anyway, thank you again for creating CM6! This is amazing how many complex things it can handle with no performance issues.