Skip to content

#560: GTK editor click lands 2–3 columns left of the clicked glyph — vimcode re-derives quadraui's text-layout inverse (needs quadraui hit-test API) - #562

Merged
JDonaghy merged 4 commits into
developfrom
issue-560-gtk-editor-click-lands-2-3-columns-left
Jul 6, 2026
Merged

#560: GTK editor click lands 2–3 columns left of the clicked glyph — vimcode re-derives quadraui's text-layout inverse (needs quadraui hit-test API)#562
JDonaghy merged 4 commits into
developfrom
issue-560-gtk-editor-click-lands-2-3-columns-left

Conversation

@JDonaghy

@JDonaghy JDonaghy commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Closes #560

Automated merge from the coordinator for assignment 20559e893aa2 on issue #560.

Worker branch: issue-560-gtk-editor-click-lands-2-3-columns-leftdevelop.

JDonaghy and others added 4 commits July 6, 2026 02:17
…aui's shared text-layout inverse

quadraui#420 added Backend::editor_col_at_x / EditorLayout::col_at_x — a
backend-owned inverse of the same layout draw_editor paints with. This
wires vimcode's click handlers through it instead of hand-rolled math:

- src/render.rs: new editor_text_layout() builds the Editor+EditorLayout
  pair exactly as paint does (to_q_editor + Editor::layout(editor.rect)),
  shared by both backends.
- src/gtk/click.rs: pixel_to_click_target's TextArea arm now calls
  backend.editor_col_at_x(...) instead of building a second,
  attribute-less Pango layout and running xy_to_index by hand — the root
  cause of the 2-3 column left-drift on bold/italic/font_scale spans and
  scrolled lines. Threaded a GtkBackend reference through
  pixel_to_click_target/handle_mouse_click/handle_mouse_double_click/
  handle_mouse_drag, dropping the now-redundant `pango_layout` param and
  the App::editor_pango_layout helper that built it.
- src/tui_main/mouse.rs: the three TextArea click/drag/hover sites now
  call EditorLayout::col_at_x (the same function GTK's default falls
  back to) instead of duplicating the scroll_left/segment_col_offset
  arithmetic inline, so both backends' column math derives from one
  quadraui function.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…resolution

The round-2 smoke test on the #560 fix (routing GTK click-column
resolution through quadraui's editor_col_at_x) reported a NEW
regression: clicks on markdown lines with emoji drifted one column
right per preceding wide/multi-byte glyph, compounding across the
line, while plain-text clicks stayed exact.

Root-cause investigation (recorded in the issue's durable findings)
reproduced the exact symptom shape only when GtkBackend::editor_col_at_x
falls back to EditorLayout::col_at_x's naive uniform-monospace division
(the fallback path used when no Pango layout is available) — the real
per-glyph quadraui::gtk::editor_col_at_x (Pango xy_to_index) path was
verified byte-exact for the reported repro string, both in isolation
and now end-to-end through vimcode's actual render pipeline (real
md_inline_spans bold-span byte offsets via build_screen_layout, then
render::editor_text_layout + quadraui::gtk::editor_col_at_x).

This strongly points to the smoke-tested artifact having been built
against the stale/broken quadraui checkout noted in the issue's pinned
environment finding (which predated the #420 merge this fix depends
on), not a logic bug in the current quadraui HEAD or in vimcode's
render.rs span-offset pipeline — both were re-verified correct here.

Adds a regression test pinning the full pipeline against a real
markdown line with base, astral-plane, and variation-selector emoji so
a future reintroduction of the naive fallback (or a span byte-offset
regression) fails `cargo test`, not just a manual click.

Also includes a pre-existing rustfmt fixup in render.rs picked up by
the mandatory `cargo fmt` pass, unrelated to this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…l-backend fix)

Iteration 2 root cause. The GTK editor-click emoji drift (click on a glyph
after an emoji lands +1 column right; compounds per preceding wide glyph;
plain monospace unaffected) is NOT in quadraui's per-glyph inverse — it is the
dual-backend split introduced by the #540 ShellApp migration.

quadraui's `gtk::run` runner creates its OWN `GtkBackend`, paints the editor
with it (stashing `last_editor_pango_layout`), and passes it to
`render_content` as the `&mut dyn Backend` arg. vimcode's `App` keeps a
SEPARATE `self.backend` (mod.rs) used only for click hit-testing: it never
paints and was never handed a Pango context. So at click time,
`pixel_to_click_target -> self.backend.editor_col_at_x` found
`last_editor_pango_layout = None` AND `pango_ctx = None`, and fell through to
`EditorLayout::col_at_x`'s naive uniform-cell division — exact for monospace,
but off by one column per preceding wide glyph (emoji/CJK). The prior fix's
regression test called `quadraui::gtk::editor_col_at_x` directly with a
correctly-fonted layout, so it verified the good branch while the live click
took the naive one.

Fix (thin wiring, mirrors what the runner does for its own backend):
`render_content` now builds an editor-fonted PangoCairo context each frame and
calls `self.backend.set_pango_context(...)`, so the click backend resolves
columns through `quadraui::gtk::editor_col_at_x`'s exact per-glyph `xy_to_index`
path instead of the naive division.

Tests (src/gtk/click.rs): drive the REAL `GtkBackend::editor_col_at_x` trait
method (with its fallback branch-selection), not the free function —
- `live_trait_..._after_paint`: steady-state path via a stashed layout;
- `editor_col_at_x_falls_back_to_editor_font_context_not_naive_division`:
  no paint, only `set_pango_context` (the live click state), proving the fix
  keeps resolution on the per-glyph path.
Emoji here render 1.7-2.3x cell width, so a naive division would fail both.

Note: `src/gtk/draw.rs::draw_editor` is dead since #540 (no callers); the live
paint path is `render_content`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tings

Iteration-3 smoke failure root cause. The manual smoke test showed editor
clicks landing LEFT of the target on plain, bold, italic AND scrolled lines,
the drift GROWING the further right the click — not a constant fudge. That
growth signature is a cell-width SCALE error, not the emoji-only +1-per-glyph
fallback the iteration-2 fix addressed.

The quadraui ShellApp runner (`quadraui::gtk::run`) paints the editor with a
hardcoded "Monospace 11", ignoring `settings.font_family`/`font_size`; that
painted '0' advance is what `Backend::char_width()` (== `cached_char_width`)
reports and what `build_screen_layout` / `editor_text_layout` positioned
glyphs with. The iteration-2 fix, however, fonted the click backend's Pango
context from `settings.font_size` (default 14). So `editor_col_at_x`'s
`xy_to_index` measured against glyphs ~1.27x too wide and scaled every column
down — clicking column c resolved to ~c*11/14, i.e. left-drift growing with x
(~1 char near line start, ~4 at the end). Emoji lines drifted too, stacked on
the base scale error; horizontal scroll made it worst-magnitude.

Fix: `App::render_content` now builds the click context via the new
`click::build_editor_click_context(cw)`, which mirrors the runner's monospace
family and tunes only the point size until a probe '0' advance equals the
painted `char_width` (`cw`). Because it is the same family at the reproduced
size, ALL glyph advances — plain, bold/italic (per-span attrs), emoji/CJK
fallback — line up with the paint, so `xy_to_index` inverts exactly.

The earlier emoji tests used ONE self-consistent font for both paint and
resolve, so they never exercised the paint≠settings size split. New test
`click_context_matches_painted_font_not_settings_size` reproduces it: paints
at one size, resolves through the production-built context on a long plain
ASCII line, asserts every column exact, and pins via `bad_drift_seen` that a
size-mismatched context genuinely drifts (so the test can't pass trivially).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

GTK editor click lands 2–3 columns left of the clicked glyph — vimcode re-derives quadraui's text-layout inverse (needs quadraui hit-test API)

1 participant