Skip to content

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

Open
VictorVow wants to merge 2 commits 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 2 commits into
logseq:masterfrom
VictorVow:fix/cursor-eol

Conversation

@VictorVow
Copy link
Copy Markdown
Contributor

@VictorVow VictorVow commented May 18, 2026

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.

VictorVow added 2 commits May 18, 2026 09:11
When the caret was at the end of a block's line and the user pressed
Up/Down to move to the adjacent block, cross-boundary navigation
captured only the numeric column offset and replayed it verbatim in the
target block. If the target line was longer than that column, the caret
landed mid-line instead of staying at the end of the line.

Add a `cursor/end-of-line?` predicate (symmetric with the existing
`beginning-of-line?`). `move-cross-boundary-up-down` now passes an `:end`
sentinel instead of the column when the source caret is at end-of-line,
and `text-range-by-lst-fst-line` maps `:end` to the whole content (`:up`
-> end of the target's last line) or its first line (`:down` -> end of
the target's first line). The numeric-`pos` path is unchanged, so
mid-line up/down still preserves the column.

Add unit coverage for `text-range-by-lst-fst-line` over both directions,
the `:end` sentinel, and a numeric-pos non-regression case.
Multi-line blocks lost end-of-line stickiness when navigating Up/Down
between hard lines inside the same block: the pixel/mock-text based
next-cursor-pos-up-down only matches the nearest horizontal position, so
from the end of one line the caret landed mid-line on a longer adjacent
line instead of at its end.

This "If EOL, always move cursor to previous/next EOL" behavior existed
in the original newline-arithmetic implementation but was dropped in
237857a when up/down moved to pixel-position navigation (which was
needed for soft-wrapped rows and proportional/wide glyphs).

Restore it as a targeted guard rather than a revert: add a pure
cursor/adjacent-line-end-pos helper and, in move-cursor-up-down, 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.

Add frontend.util.cursor-test covering adjacent-line-end-pos across both
directions, no-adjacent-line cases, single-line content, and the
land-at-end-regardless-of-length case. Companion to e49f5ad, which
fixed the same behavior across block boundaries.
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