Preserve raw include diagnostic provenance and derive bands from the existing shaped editor. Retain deferred tooltip paragraphs through rendering. Include source, regression, failure and native-trial evidence; corrected native acceptance remains pending.
13 KiB
Diagnostic editor geometry — pinned API research
Read-only research for #12 at clean codex/text-diagnostics-9e8r6t, source b4b860f2776aa8251306d83ff58362c0359176ef. No source/dependency edits, build, GUI, network or application execution. Parent owns diagnostic publication and wrapper integration; span normalization is separately delegated.
Finding: the pinned renderer already contains exact visible shaped runs, but stock text_editor::Content does not expose its editor. A normal advanced wrapper cannot obtain that geometry through Content or the public Widget operations. Prototype a narrow read-only Content accessor plus a permanent delegating wrapper; measure it before choosing a maintained dependency patch. Do not rebuild or manipulate editor selection merely to obtain annotation positions.
Primary APIs and the access gap
Primary root: C:/Users/User/Documents/Git/ARCADIA-Studio/.local/toolchain/cargo/registry/src/index.crates.io-1949cf8c6b5b557f/.
iced_widget-0.14.2/src/text_editor.rs:383–492:Content<R>(RefCell<Internal<R>>)contains privateInternal.editor: R::Editor. Public APIs are text/lines, cursor, copied selected text, perform/move_to, line ending and empty check. There is no geometry accessor or public editor borrow.Cloneconstructswith_text(self.text())and loses live editor state; it is not a geometry snapshot.- Same file
:648–660: actual layout callseditor.updatewith padded inner bounds, selected font/size/line-height/wrapping and actual highlighter.:939–1070: draw callseditor.highlightbeforefill_editor(&internal.editor, text_bounds.position(), color, text_bounds). It then draws cursor/selection translated by the same padded origin and clipped to text bounds. Therefore app-level line-number × line-height arithmetic ignores wrapping and cannot describe actual glyph positions. iced_core-0.14.0/src/text/editor.rs:9–79: theEditortrait exposes current selection geometry (Selection::Caret(Point)orRange(Vec<Rectangle>)), current cursor, text and update/highlight, but no arbitrary-range geometry or read-only hit test. Current selection geometry cannot be used for diagnostics without temporarily changing the user's real selection. That is unacceptable after the verified Word/Line/Find regressions.iced_core-0.14.0/src/text/highlighter.rs:97–109:Format<Font>contains only optional color and font.Highlighteryields line-relative byte ranges, but cannot request squiggles, background areas, hover IDs or range hit testing. Using a diagnostic font change would also alter metrics and syntax appearance. Foreground tint alone is a distinct, limited feature rather than an implementation of underlines/tooltips.iced_graphics-0.14.0/src/text/editor.rs:31–38: public concreteEditor::buffer(&self) -> &cosmic_text::Buffer. This is the existing actual laid-out buffer, including scroll and highlighted font attributes.:141–275computes current-selection geometry internally; it is not an arbitrary-span public API.cosmic-text-0.15.0/src/buffer.rs:23–95: visibleLayoutRunexposes original logicalline_i,text, glyphs, paragraph RTL,line_y,line_top,line_height, width andhighlight(start_cursor,end_cursor).:99–176shows the iterator starts atbuffer.scroll.lineand subtractsscroll.verticalfrom each run's line top; subtracting scroll a second time would be wrong. Multiple wrapped runs retain the same logical line index.- Same
buffer.rs:883–988:Buffer::layout_runs()and read-onlyBuffer::hit(x,y)are public. Hit uses actual visible runs/glyph clusters and grapheme subdivisions, returning logical line/byte index and affinity. It also snaps points outside glyphs to a nearest line endpoint; a returned cursor alone does not prove the pointer is over an annotated glyph. cosmic-text-0.15.0/src/layout.rs:12–55:LayoutGlyphexposes logical clusterstart/endUTF-8 byte indexes, physical advance hitboxx/y/w, bidi level, font ID/size and shaping offsets. Glyph ink and advance rectangles differ, especially for combining marks/italic overhang; define the diagnostic hover area as a visual advance band rather than claiming exact ink outlines.iced_core-0.14.0/src/text.rs:358:Renderer::fill_editorreceives the concrete associated editor, position and clip bounds. A renderer adapter could intercept this real call, but a plain Widget wrapper receives the existing renderer type and cannot override its implementation.
Material alternatives
| Candidate | Alignment and input preservation | Cost / decision |
|---|---|---|
| Stock highlighter plus ordinary wrapper | Syntax color ranges are exact, but no underline geometry or read-only hover mapping. Wrapper can know bounds, not private shaped buffer. | Useful only if the product intentionally chooses color-only annotations; does not satisfy full geometry goal. |
| Mirror text into a second Editor/Paragraph | Can use public graphics APIs, but must reproduce exact fonts, highlighter attrs, padding, wrap, scroll, viewport and post-highlight shaping. Cursor/IME auto-scroll and edits easily desynchronize it. | Reject as the primary architecture: duplicates text/layout authority and makes passing synthetic tests insufficient. No selected-string search or fixed monospace width heuristic. |
| Small read-only Content extension + permanent wrapper | Reads the actual editor after actual layout/draw, without moving cursor, replacing Content or intercepting edit semantics. | Recommended bounded experiment. Requires a reviewed pinned dependency patch (not an edit to global registry cache), license/package notice handling and upgrade ownership if adopted. |
| Renderer interception | fill_editor has exact editor/origin/clip and avoids a Content accessor. |
Feasible supported API route with a custom renderer/decorator integrated at the renderer type boundary. Content<R> and all Element/renderer bounds must agree; cannot silently wrap renderer just for a child whose Content has another R. Broader type plumbing and output interception than the accessor; assess only if the parent already has an appropriate renderer seam. |
| Custom/forked whole text editor | Full control over geometry, events, hover and decorations. | Disproportionate: must maintain selection gestures, IME, clipboard, focus guards, mouse capture and rendering. Do not replace a tested editor merely for annotations. |
A candidate accessor can be backend-neutral and small: Content::with_editor<T>(&self, f: impl FnOnce(&R::Editor) -> T) -> T, borrowing internally only for the closure. This is a proposed extension, not an API that exists today. Alternatively expose a safe mapped read-only borrow with documented lifetime. Never expose mutable editor access, use unsafe field access, downcast private Internal, or return a cloned editor as a substitute. The app can compute owned visible geometry inside the callback and release the borrow before further child draw/update; do not nest calls to Content or draw while its RefCell borrow is held. Merely exposing the core trait is insufficient for arbitrary spans: the app's concrete renderer-associated Editor must be the public graphics Editor or an equivalent supported geometry adapter.
Exact mapping and hover contract for the experiment
- Accept only current, admitted logical line/UTF-8 byte ranges from the independently designed annotation snapshot. Bind publication/workspace/path/text revision and editor layout generation. Do not display a fresh diagnostic on stale shaped text after an edit.
- Delegate normal child layout and draw unchanged. Read the actual buffer after the child has applied its highlighter in draw; layout-only geometry can precede a font/style-dependent reshape. Preserve exactly one editor Tree child and the existing sensor/raw-input wrapper placement. Do not rebuild Content or alter its cursor/scroll for annotations.
- Iterate visible layout runs only. Intersect each logical diagnostic range with the actual glyph cluster ranges in each matching logical line. Produce separate visual segments for wrapped runs and discontinuous bidi areas; merge only adjacent/overlapping x intervals on the same run. A single min/max rectangle across all bidi glyphs may incorrectly underline unrelated text.
LayoutRun::highlightis convenient for a simple logical span but returns only one x interval and compares glyph-edge cursors/affinity. A span ending inside a multi-codepoint cluster may not yield an appropriate visual interval. Explicitly define cluster expansion for decorations (any intersected cluster gets its visual band), while retaining the exact source range in the diagnostic authority. Do not silently call this byte-exact ink coverage. Empty spans/newline-only ranges need a separate visible insertion marker policy and must not borrow an unrelated token's meaning.- Translate once by the same padded text origin used by
fill_editor, and intersect with inner text bounds and ancestor viewport/layer clips. Work in logical coordinates; let the renderer apply window scale/ancestor transforms. Never subtract buffer scroll again or multiply coordinates by DPI twice. All underlines/hover regions outside clips are suppressed. - Prefer hover hit testing against the visible clipped annotation bands already computed from glyph clusters.
Buffer::hitmay help resolve logical position but needs a band containment check because it snaps whitespace/outside text to endpoints. Overlapping annotations retain all corresponding identities for details; do not choose a hidden unrelated range. Do not synthesizeAction::Clickon hover. - Any cached hover geometry must be invalidated on source revision, publication/path change, resize, wrap/font/theme change and scroll, including caret/IME auto-scroll. If pointer input arrives after layout changes and before fresh geometry, hide the tooltip rather than act on stale coordinates. Pass ordinary pointer/keyboard/edit events through to the existing editor. Tooltip opening must not steal focus, change selection, create model edits or consume normal scrolling. Parent decides diagnostic Guard publication/visit authority; geometry does not replace it.
Recommended first experiment and falsifiers
Prepare a private small dependency accessor patch and a permanently mounted wrapper around the existing real graphics-backed editor. No global cache patch. Record underlines/hover bands against the same actual fill_editor buffer/origin/clip used by the evaluator. Before downstream UI implementation, demonstrate:
- A multiline span crossing two wraps with a nonzero pixel scroll, partial top/bottom visibility, and a resize that changes wrap count. Every expected visible cluster is covered, off-viewport glyphs are not; no double-scroll offset.
- Repeated identical aliases at separate locations, CRLF/mixed endings, tabs, long unbroken text, Unicode combining clusters, CJK and a mixed RTL/LTR line. Diagnostics map by source identity, never first textual match; separate bidi segments do not cover unrelated glyphs.
- Empty/EOF/newline-only ranges have a specified bounded marker; out-of-range/non-UTF8-boundary locations are rejected by admission. A pointer in blank editor space must not show a nearby diagnostic solely because Buffer::hit snaps there.
- Pointer hover, popup dismissal, typing, double/triple click, drag, Find Close/Escape, IME preedit and scroll leave existing Content/selection/focus/history semantics intact. Reuse four focus regressions and the native selected-byte probes for the eventual integration rather than inventing weaker cursor-equality tests.
- A diagnostic publication or source edit arriving under a stationary pointer immediately invalidates old hover, without moving the editor or silently remapping stale ranges. Changing font/highlighter settings and ancestor clip after a cached frame cannot leave stale hitboxes.
- A long file with many offscreen diagnostics processes only indexed visible candidates and produces a bounded visible segment set, rather than reshaping a second whole document or scanning every glyph of every line each mouse move.
Reject the candidate if geometry comes from a duplicate buffer, if it requires mutating selection to measure spans, if missing/stale geometry remains actionable, or if real input/Tree continuity changes. CPU evaluator success establishes alignment in that renderer path; native minimum/wide DPI/scroll and hover checks are still required before UX acceptance. Stop further API research after this experiment resolves the accessor/geometry seam.
Source bindings
- iced_widget text_editor.rs:
cecf8686c90aa92066d55075beba545dfb7dd5a44bad0c1519fcb110a56070e1. - iced_graphics text/editor.rs:
b12c4e645a4db26c79e421a88c2127c8c20b7ff0f5a6fae9a8e050f40bf39567. - iced_core text/editor.rs:
717158d90d372183ee07483ad950c648fe79acdaa3c61db4d006b924426d0eee. - iced_core text/highlighter.rs:
b92800869452e91668e588654f2356b58994cf2e00ffd9b6e0b48ef21e627387. - cosmic-text buffer.rs:
c715f18a4288cfc15c1d919a1998146d4d44072704bd6d24dfc30811149647d8. - cosmic-text layout.rs:
49f6321ca047f8c9e1ac3f0d596be96f9c4d22cc623de29cdf61e2806501a770.