Skip to content

editor: Judge horizontal on-screen position in rendered space - #62310

Closed
4ktLuffy wants to merge 1 commit into
zed-industries:mainfrom
4ktLuffy:fix/newest-selection-on-screen-rendered-x
Closed

editor: Judge horizontal on-screen position in rendered space#62310
4ktLuffy wants to merge 1 commit into
zed-industries:mainfrom
4ktLuffy:fix/newest-selection-on-screen-rendered-x

Conversation

@4ktLuffy

@4ktLuffy 4ktLuffy commented Aug 7, 2026

Copy link
Copy Markdown

Closes #62308.

Problem

Editor::newest_selection_on_screen reports a cursor as off screen when enough multi-byte text precedes it on the line, even though it is plainly visible. Vim's Ctrl-D / Ctrl-U call it to decide whether to move the cursor, so the cursor is left behind.

Root cause

crates/editor/src/scroll.rs compared two values in different units:

newest_head.column() <= screen_top.column() + visible_columns as u32

DisplayPoint wraps BlockPoint(pub Point), so .column() is a byte offset. visible_column_count is set from editor_width / em_advance in EditorElement (crates/editor/src/element.rs), a count of rendered cells. Byte offsets meet or exceed rendered columns for any non-ASCII text, so the comparison fails on lines it should accept.

Fix

autoscroll_horizontally already answers the same question correctly, by mapping the column through the line layout with x_for_index. DisplaySnapshot::x_for_display_point is exactly that mapping, so this uses it for both the head and the screen top and compares the pixel delta against em_advance * visible_columns — the same width visible_column_count was divided out of.

Proof

The test pairs a 40-character multi-byte line with an ASCII line of identical rendered width, in a 60-column window. Both must be judged on screen, and both must agree.

Without the change:

test result: FAILED. 0 passed; 1 failed
assertion failed: a multi-byte line and an ASCII line of the same rendered width
must agree; got multibyte=Greater vs ascii=Equal

With the change:

test result: ok. 1 passed; 0 failed

The ASCII control is asserted separately, so a harness that silently computed nothing would fail rather than pass.

Scope and risk

  • cargo test -p editor — 887 passed, 0 failed
  • cargo test -p vim — 562 passed, 0 failed
  • cargo fmt --all --check and cargo clippy -p editor -p vim --all-targets clean

Behaviour only changes for the case that was wrong: a cursor whose rendered position is inside the viewport but whose byte column exceeded the column budget. ASCII text is unaffected, since there byte offsets and rendered columns coincide.

One decision for reviewers

newest_selection_on_screen now takes a &mut Window, because reaching the text layout requires one. There is a single in-tree caller (crates/vim/src/normal/scroll.rs) and it already has a window in scope, but this is a pub fn on Editor, so it is a breaking change for any out-of-tree caller.

The alternative is to store the viewport width in pixels alongside visible_column_count and compare against that, which avoids the signature change at the cost of duplicating state that is already derivable. Happy to switch if you prefer that.

Related

#62305 fixes the same class of bug — byte offsets used where rendered position is meant — in align_selections. Different file, different command; the two do not overlap and can land independently.

`newest_selection_on_screen` compared `DisplayPoint::column()`, a byte
offset, against `visible_column_count`, which `EditorElement` derives as
`editor_width / em_advance` — a count of rendered cells. Byte offsets meet
or exceed rendered columns for any non-ASCII text, so a cursor that is
plainly on screen is reported as off it once enough multi-byte text
precedes it on the line. Vim's Ctrl-D / Ctrl-U read that answer to decide
whether to move the cursor, so the cursor gets left behind.

`autoscroll_horizontally` already answers the same question correctly, by
mapping the column through the line layout. `x_for_display_point` is that
mapping, so this uses it for both the head and the screen top and compares
the pixel delta against `em_advance * visible_columns` — the same width the
column count was derived from.

`newest_selection_on_screen` needs a `Window` to reach the text layout and
so takes one; its single in-tree caller already has one in scope.

The test pairs a 40-character multi-byte line with an ASCII line of the
same rendered width in a 60-column window. It fails without this change
(multibyte=Greater vs ascii=Equal) and passes with it, and asserts the
ASCII control independently so a harness that computed nothing would not
pass.
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 7, 2026
@zed-community-bot zed-community-bot Bot added the first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions label Aug 7, 2026
@smitbarmase smitbarmase added the area:editor Feedback for code editing, formatting, editor iterations, etc label Aug 7, 2026
@SomeoneToIgnore

Copy link
Copy Markdown
Contributor

Thank you for the PR, the issue seem to have turned out to be a bit more complex and #61997 in the end got to fix it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:editor Feedback for code editing, formatting, editor iterations, etc cla-signed The user has signed the Contributor License Agreement first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions

Projects

None yet

3 participants