editor: Align selections on rendered position rather than byte offsets - #62305
Closed
4ktLuffy wants to merge 1 commit into
Closed
editor: Align selections on rendered position rather than byte offsets#623054ktLuffy wants to merge 1 commit into
4ktLuffy wants to merge 1 commit into
Conversation
`align_selections` computed padding from `Point::column`, which is a byte offset, so any row wider in bytes than it is on screen reported a larger column than it occupied and received the wrong number of spaces. Using the example from zed-industries#60192, where `←` is 3 bytes and `π` is 2: a ← 1 # one prefix: 9 bytes, 7 columns bc ← π # two prefix: 11 bytes, 8 columns The target was taken as max(9, 11) = 11 bytes, so the first row gained two spaces and landed at column 9 while the second stayed at column 8. Measure where each cursor actually renders instead, the same approach zed-industries#57097 used for columnar selection. Padding is always spaces, so a row's accumulated shift is exactly space_count * space_width and the per-column running offset stays exact. This is not only a multi-byte problem. Two further cases were found while testing and are covered by the new test: - Hard tabs. "a\t" is two bytes but renders out to the tab stop, so byte offsets treated it as no wider than "bb" and inserted no padding at all. - Rows that were already aligned were pulled apart. "ãa " and "bb " both render three columns wide but are 4 and 3 bytes, so the second row was padded and a correct alignment was broken. test_align_selections_multibyte covers a pure-ASCII control that passes with and without the change, a 2-byte character, the reported case, the already-aligned case, several cursors per row, and hard tabs. Reverting only editor.rs makes it fail; the two existing align tests are unaffected in both directions.
Contributor
|
Thank you, closing in favor of already created #61997 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
Fixes #60192.
Editor::align_selectionscomputed padding fromPoint::column, which is a byte offset. Anyrow that is wider in bytes than it is on screen reports a larger column than it occupies, so
the wrong number of spaces is inserted.
Using the example from the issue, where
←is 3 bytes andπis 2:The target is taken as
max(9, 11) = 11bytes, so the first row gains two spaces and landsat column 9 while the second stays at column 8.
This turned out to be broader than the report. The underlying fault is
bytes-versus-rendered-position, and multi-byte characters are one of three ways to reach it:
Hard tabs.
a\tis two bytes but renders out to the tab stop, so byte offsets treat it asno wider than
bband insert no padding at all:Rows that were already aligned get pulled apart.
ãaandbbboth render three columnswide but are 4 and 3 bytes, so the previous behaviour padded the second row and broke a
correct alignment:
Solution
Measure where each cursor actually renders, using
x_for_display_point, and convert the gapback into spaces with the space advance.
This is the same approach as #57097, which fixed the identical problem for columnar selection.
Because padding is always spaces, a row's accumulated shift is exactly
space_count * space_width, so the per-column running offset stays exact.Testing
test_align_selections_multibytecovers, in order:without the change — it is there to show the test is not simply red.
Reverting only
editor.rsand re-running gives:The two existing align tests (
test_align_selections,test_align_selections_multicolumn)pass unchanged in both directions, which is consistent with them being ASCII-only and never
having exercised this path.
Tested on macOS (Apple Silicon).
cargo fmtandcargo clippy -p editor --testsare clean.Notes for review
space_widthcomes fromem_layout_width, the em advance ratherthan the measured width of U+0020. They are equal in a monospace font and differ in a
proportional one — though padding with spaces cannot align proportional text in any case.
editor: Fix columnar selection alignment on rows with multi-byte chars #57097 makes the same assumption via the same call.
(gap / space_width).round()is exact when every glyph advance is a wholemultiple of the space advance, which holds for monospace. A fallback glyph of unusual width
would round to the nearest space rather than fail loudly.
x_for_display_pointis now called once per cursor, where the previous code wasinteger arithmetic.
LineLayoutCache(crates/gpui/src/text_system.rs:365) means repeatedlayouts of the same row are cached, so this should not be a per-cursor re-layout.
showed no regression — the previous behaviour was wrong there too — but I did not confirm
the line had actually wrapped, so I have not shipped that as a test.
One question for maintainers
The tab case now aligns by inserting spaces. Where
hard_tabsis enabled, spaces may notbe what you want, even though it matches this action's existing behaviour. Happy to change it
to tabs, or to drop the tab case from this PR and raise it separately — whichever you prefer.
Self-Review Checklist:
Showcase
Aligning the
#symbols in the issue's example: