Skip to content

core, rust: step to the character before an extmark, not into it - #2050

Open
simonklee wants to merge 4 commits into
mainfrom
harden/607-left-over-extmark-lands-inside-wide-char
Open

simonklee wants to merge 4 commits into
mainfrom
harden/607-left-over-extmark-lands-inside-wide-char

Conversation

@simonklee

Copy link
Copy Markdown
Member

Left from the end of a virtual extmark (an @file mention in OpenCode's prompt) jumped to one cell
before the mark. Offsets are display cells, so after a CJK character, an emoji or a tab the cursor
landed on that character's second cell: it was drawn mid-glyph, and the next keystroke went in front
of the character. A word move back into the mark and Up or Down landing in it nearer its start used
the same target. The Rust Textarea copies these moves for its atomic marks and had the same bug.

These moves now set the cursor to the mark's start and take one Left step, so the editor's
grapheme boundaries decide the target. Marks that touch or overlap are skipped together
on all of these moves, and Up or Down that goes to a mark's end also moves past a later mark that
overlaps that end, instead of stopping inside it.

Tested with a table over the character before the mark and each move, in Core and in the Rust
Textarea, plus rows for wrapped, touching and overlapping marks. A move over a mark can now
publish more cursor-changed events, all with the final position.

Left from the end of a virtual extmark, a word move back into it, and
Up or Down landing in it nearer its start all put the cursor at
start - 1. Offsets are display cells, so after a CJK character, an
emoji or a tab that is the character's second cell: the cursor is drawn
mid-glyph and the next keystroke goes in front of the character.

These moves now set the cursor to the mark's start and take one Left
step, which lands on the start of the character before it; the editor
already knows where that is. If that step ends in another virtual mark,
which touches or overlaps this one, the cursor moves before that one
too, as Left did before. Up and Down share one helper, so the "Down
never goes back" check compares the real position.

The table rows replace five tests that checked the same narrow moves
with weaker assertions.

Issue: 607
The Textarea copies Core's virtual extmark moves for its atomic marks,
and with them the start - 1 target: Left from a mark's end, or Up or
Down landing in it nearer its start, put the cursor on the second cell
of a CJK character, an emoji or a tab before the mark.

These moves now set the cursor to the mark's start and send one
MoveLeft, as Core does. The new move_before helper holds that rule for
both moves, which is where the added lines go.

Issue: 607
When Up or Down lands in a virtual extmark and goes to its end, that end
can be inside a later virtual extmark that overlaps this one. The cursor
stopped there, and the next keystroke edited the mark's text. Text after
a wide character makes this more likely now that Down from before a
wrapped mark goes to its end, not into the character.

The end now moves on past every overlapping mark. Rows cover a wrapped
mark with and without one, a mark at a line start, a move nearer the
end, and the selection bypass, which nothing covered.

Issue: 607
…skip

When Up or Down lands in an atomic extmark and goes to its end, that end
can be inside a later atomic mark that overlaps this one.
keep_cursor_outside moves the cursor out of one such mark, toward the
edge it came from, so a chain of overlapping marks still held it: Down
over four of them stopped inside the fourth.

The end now moves on past every overlapping mark, as Core's does; the
loop is the added source. Rows cover a wrapped mark with and without
the chain, a mark that only touches the end, a mark at a line start,
and a move nearer the end.

Issue: 607
@simonklee
simonklee requested a review from kommander as a code owner November 12, 2026 13:25

This branch has not been deployed

No deployments
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