I wonder if there’s an API to translate between document positions (code point offset) and view line, column
positions (which would respect line wrapping)?
My use case is I’m trying to organise vertical movement between several CodeMirror instances while keeping the cursor at the same column offset. Imagine a notebook like UI where each cell is a CodeMirror instance, now if I go to the next cell (down) then I want to end at the first line / same column, if I go to the previous cell (up) then I want to end at the last line / same column.
What I have written so far is some code to use posAtCoords/coordsAtPos
to determine the column offset:
export function getEditorViewColumn(view: View.EditorView): number {
let pos = view.state.selection.main.head;
let left = view.coordsAtPos(pos)?.left ?? 0;
let col = view.posAtCoords({ x: left, y: 0 });
return col ?? 0;
}
And then setting the cursor for the desired column offset at the first/last line:
function setCursorByGoal(
view: View.EditorView,
goal: { line?: null | "first" | "last"; col: number }
) {
let line = goal.line ?? "first";
if (view.state.doc.length === 0) {
return;
}
let pos = 0;
if (line === "last") {
let col = Math.min(goal.col, view.state.doc.length - 1);
// The last visual line
let lastLine: View.BlockInfo | null = null;
view.viewportLines((line) => (lastLine = line));
// `x` of the first line
let x = view.coordsAtPos(col, -1)?.left;
// `y` of the last line
let y = (lastLine as View.BlockInfo | null)?.bottom;
pos = view.posAtCoords({ x: x ?? 0, y: y ?? 0 }) ?? 0;
} else {
pos = Math.min(goal.col, view.state.doc.length - 1);
}
let selection = State.EditorSelection.cursor(pos);
view.dispatch(
view.state.update({
selection,
scrollIntoView: true,
annotations: [
State.Transaction.userEvent.of("keyboardselection"),
FocusFromKeyboard.of(line),
],
})
);
}
But I’m wondering if there’s a cleaner way… I don’t think I’ve taken into account all edge cases here…