Skip to content

fix(editor): keep caret at end-of-line on Up/Down navigation#12674

Open
VictorVow wants to merge 1 commit into
logseq:masterfrom
VictorVow:fix/cursor-eol
Open

fix(editor): keep caret at end-of-line on Up/Down navigation#12674
VictorVow wants to merge 1 commit into
logseq:masterfrom
VictorVow:fix/cursor-eol

Conversation

@VictorVow

@VictorVow VictorVow commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • When the caret is at the end of a line and you press Up/Down, it now stays at the end of the target line — both when crossing block boundaries and when moving between hard lines inside a multi-line block.
  • Before: cross-boundary Up/Down replayed only the numeric column, and within-block Up/Down used pixel-nearest matching, so on a longer adjacent line the caret landed mid-line instead of at its end.

Details

  • Add cursor/end-of-line? predicate, symmetric with the existing beginning-of-line?.
  • Cross-block (handler/editor.cljs move-cross-boundary-up-downhandler/block.cljs text-range-by-lst-fst-line): pass an :end sentinel when the source caret is at EOL, so it lands at the end of the adjacent block's edge line (last line for :up, first line for :down). The numeric-column path is unchanged.
  • Within-block (util/cursor.cljs move-cursor-up-down): when at EOL, jump to the end of the adjacent hard line via the new pure adjacent-line-end-pos; otherwise fall back to the existing pixel/mock-text navigation, so soft-wrapped rows and mid-line movement are unchanged. select-up-down is untouched.
  • This re-adds the "If EOL, always move cursor to previous/next EOL" behavior that existed in the original newline-based implementation but was dropped in 237857a when Up/Down moved to pixel-position navigation (needed for soft-wrapped rows and proportional/wide glyphs). It is restored as a targeted guard layered on top of pixel nav, not a revert.

Test plan

  • Unit: frontend.handler.block-test, frontend.util-test, and new frontend.util.cursor-test — 12 tests / 74 assertions, 0 failures, 0 errors.
  • clj-kondo clean on all changed files; node-test build compiles with 0 warnings.
  • Manual in a running app (not executed here):
    • Multi-line block: caret at end of an interior line, press Up/Down → caret at end of adjacent line.
    • Caret at end of a block, Up/Down across the boundary → end of the adjacent block's edge line.
    • Regression: mid-line Up/Down still preserves the visual column.
    • Regression: a long soft-wrapped line still moves by visual row on Up/Down.

Multi-line and cross-block Up/Down navigation lost end-of-line
stickiness: from the end of a line the caret landed mid-line on a
longer adjacent line (in the same block or the next/previous block)
instead of staying at the end. The EOL-sticky behavior existed in the
original newline-arithmetic implementation but was dropped in 237857a
when Up/Down moved to pixel-position navigation (needed for soft-wrapped
rows and proportional/wide glyphs).

Restore it as a targeted guard rather than a revert:

- Within a block (move-cursor-up-down): add a pure
  cursor/adjacent-line-end-pos helper; when the caret is at end-of-line,
  jump to the end of the adjacent hard line, otherwise fall back to the
  existing pixel navigation so soft-wrap and mid-line movement are
  unchanged. select-up-down is untouched.

- Across blocks (move-cross-boundary-up-down): add a cursor/end-of-line?
  predicate (symmetric with beginning-of-line?) and pass an :end sentinel
  instead of the numeric column when the source caret is at end-of-line;
  text-range-by-lst-fst-line maps :end to the end of the target's last
  line (:up) or first line (:down). The numeric-pos path is unchanged, so
  mid-line up/down still preserves the column.

Add frontend.util.cursor-test covering adjacent-line-end-pos (both
directions, no-adjacent-line, single-line, land-at-end-regardless-of-length)
and text-range-by-lst-fst-line (both directions, :end sentinel, numeric-pos
non-regression).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant