From 4f37a8660e2616a6acdc1beb0719bce973b5c07c Mon Sep 17 00:00:00 2001 From: Daneel Date: Sat, 28 Feb 2026 18:01:38 +0100 Subject: [PATCH] patches: update TESTING.txt + README.txt for 0007/0008 TESTING: sections 14 (overlay completion) + 15 (child frame completion) README: patch series listing, overlay/child frame architecture, textDidChange flag, focus restoration, new limitations --- patches/README.txt | 131 ++++++++++++++++++++++++++++++++++++++++++-- patches/TESTING.txt | 33 ++++++++++- 2 files changed, 159 insertions(+), 5 deletions(-) diff --git a/patches/README.txt b/patches/README.txt index efc9fbd..b15ef06 100644 --- a/patches/README.txt +++ b/patches/README.txt @@ -1,14 +1,26 @@ EMACS NS VOICEOVER ACCESSIBILITY PATCH ======================================== -patch: 0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch - 0002-doc-add-VoiceOver-accessibility-section-to-macOS-app.patch +patch: 0001-0008 (8 patches, see PATCH SERIES below) author: Martin Sukany -files: src/nsterm.h (+119 lines) - src/nsterm.m (+3024 ins, -147 del, +2877 net) +files: src/nsterm.h (+124 lines) + src/nsterm.m (+3577 ins, -185 del, +3392 net) doc/emacs/macos.texi (+53 lines) etc/NEWS (+13 lines) +PATCH SERIES +------------ + + 0001 ns: add accessibility base classes and text extraction + 0002 ns: implement buffer accessibility element (core protocol) + 0003 ns: add buffer notification dispatch and mode-line element + 0004 ns: add interactive span elements for Tab navigation + 0005 ns: integrate accessibility with EmacsView and redisplay + 0006 doc: add VoiceOver accessibility section to macOS appendix + 0007 ns: announce overlay completion candidates for VoiceOver + 0008 ns: announce child frame completion candidates for VoiceOver + + OVERVIEW -------- @@ -517,6 +529,104 @@ KEY DESIGN DECISIONS bottom, even though more buffer content exists below. +OVERLAY COMPLETION ANNOUNCEMENTS (Patch 0007) +---------------------------------------------- + + Overlay-based completion frameworks (Vertico, Icomplete, Ivy, etc.) + render candidates as overlay strings in the minibuffer. VoiceOver + does not see overlay content changes automatically. This patch + detects overlay candidate changes and announces the selected + candidate. + + Detection: + ns_ax_face_is_selected(face) checks whether a face name contains + "current", "selected", or "selection" (matching vertico-current, + icomplete-selected-match, ivy-current-match, etc.). Supports + both single face symbols and face lists. + + ns_ax_selected_overlay_text(b, beg, end, out_line) scans the + buffer region line by line using Fget_char_property to check + both text properties and overlay face properties. + + Overlay changes are tracked independently of text changes: + BUF_OVERLAY_MODIFF is checked in an independent if-branch (not + else-if) because Vertico bumps both BUF_MODIFF (text properties) + and BUF_OVERLAY_MODIFF (overlays) in the same command cycle. + + textDidChange flag: + hl-line-mode and similar packages update face properties (text + properties, not characters) on every cursor movement, bumping + BUF_MODIFF without changing BUF_CHARS_MODIFF. The original + else-if structure caused the modiff branch to fire (correctly + skipping ValueChanged) but also blocked the cursor-move branch + (SelectedTextChanged). A BOOL textDidChange flag decouples the + two branches: ValueChanged and SelectedTextChanged remain + mutually exclusive for real edits, but SelectedTextChanged fires + correctly when only text properties changed. + + Zoom: + The selected candidate position is stored in overlayZoomRect / + overlayZoomActive on the parent EmacsView. draw_window_cursor + uses this rect instead of the text cursor when a candidate is + active. Cleared when BUF_CHARS_MODIFF changes (user types) + or when no candidate is found. + + +CHILD FRAME COMPLETION ANNOUNCEMENTS (Patch 0008) +-------------------------------------------------- + + Completion frameworks such as Corfu, Company-box, and similar render + candidates in a child frame rather than as overlay strings. This + patch detects child frames via FRAME_PARENT_FRAME and announces + the selected candidate. + + Detection: + Child frames are dispatched in postAccessibilityUpdates before + the main tree rebuild logic. FRAME_PARENT_FRAME(emacsframe) + returns non-NULL for child frames. + + ns_ax_selected_child_frame_text(b, buf_obj, out_line) scans the + child frame buffer line by line, reusing ns_ax_face_is_selected + from patch 0007. + + Buffer switch safety: + Fbuffer_substring_no_properties operates on current_buffer, which + may differ from the child frame buffer during ns_update_end. + The function uses record_unwind_current_buffer / + set_buffer_internal_1 to temporarily switch, with unbind_to on + all three return paths after the switch. Uses specpdl_ref (not + ptrdiff_t) for the SPECPDL_INDEX return value. + + Re-entrance protection: + The accessibilityUpdating guard MUST precede the child frame + dispatch because Lisp calls in the scan function (Fget_char_property, + Fbuffer_substring_no_properties) can trigger redisplay. + BUF_MODIFF gating provides a secondary guard and prevents + redundant scans. + + Validation: + - WINDOWP / BUFFERP checks for partially initialized child frames. + - Buffer size limit (10000 chars) skips non-completion child frames + (eldoc, which-key, etc.). + + Focus restoration: + childFrameCompletionActive (BOOL on EmacsView) is set by the child + frame handler on the parent view. On the parent's next accessibility + cycle, FOR_EACH_FRAME checks whether any child frame is still + visible. If not, FocusedUIElementChangedNotification is posted on + the focused buffer element to restore VoiceOver character echo and + cursor tracking. + + Zoom: + Direct UAZoomChangeFocus (not overlayZoomRect) because the child + frame's ns_update_end runs after the parent's draw_window_cursor, + so the last Zoom call wins. + + Deduplication: + Static C string cache (lastCandidate via xstrdup/xfree) avoids + re-announcing the same candidate. + + KNOWN LIMITATIONS ----------------- @@ -545,6 +655,19 @@ KNOWN LIMITATIONS - No multi-frame coordination. EmacsView.accessibilityElements is per-view; there is no cross-frame notification ordering. + - Overlay completion (0007) face matching uses string containment + ("current", "selected", "selection"). Custom completion frameworks + with face names not containing these substrings will not be detected. + + - Child frame completion (0008) static lastBuffer pointer may become + stale if the buffer is freed and a new one allocated at the same + address. This is harmless (worst case: one missed announcement). + + - Child frame window-appeared announcement: macOS automatically + announces the window title when a child frame NSWindow appears. + This cannot be suppressed without breaking VoiceOver focus tracking + or Zoom integration. + - GNUstep is explicitly excluded (#ifdef NS_IMPL_COCOA). GNUstep has a different accessibility model and requires separate work. diff --git a/patches/TESTING.txt b/patches/TESTING.txt index d063e12..46dfd02 100644 --- a/patches/TESTING.txt +++ b/patches/TESTING.txt @@ -11,13 +11,15 @@ Base: emacs master (upstream HEAD at time of test) 1. Patch Application -------------------- -PASS — All 6 patches applied cleanly via git-am: +PASS — All 8 patches applied cleanly via git-am: 0001 ns: add accessibility base classes and text extraction 0002 ns: implement buffer accessibility element (core protocol) 0003 ns: add buffer notification dispatch and mode-line element 0004 ns: add interactive span elements for Tab navigation 0005 ns: integrate accessibility with EmacsView and redisplay 0006 doc: add VoiceOver accessibility section to macOS appendix + 0007 ns: announce overlay completion candidates for VoiceOver + 0008 ns: announce child frame completion candidates for VoiceOver No conflicts, no warnings. @@ -111,3 +113,32 @@ PASS — Ran 1 test, 1 result as expected: - ns-accessibility-enabled is bound OK - ns-accessibility-enabled defaults to t OK (ERT 1/1 passed, 2026-02-28 11:45:55 CET) + +14. VoiceOver — Overlay Completion (Patch 0007) +------------------------------------------------ +PASS — Vertico minibuffer overlay completion: + - Vertico candidates announced on C-n / C-p navigation OK + - Selected candidate face detected (vertico-current) OK + - Deduplication: same candidate not re-announced OK + - Zoom tracks selected candidate in minibuffer + (overlayZoomRect / overlayZoomActive lifecycle) OK + - overlayZoomActive cleared on text input OK + - hl-line-mode compatibility: cursor movement in dired + and read-only buffers correctly announces lines + (textDidChange flag decouples modiff branch from + cursor-move branch) OK + +15. VoiceOver — Child Frame Completion (Patch 0008) +---------------------------------------------------- +PASS — Corfu child frame completion: + - Corfu popup candidates announced via VoiceOver OK + - Selected candidate face detected (corfu-current) OK + - Zoom tracks selected candidate in child frame + (direct UAZoomChangeFocus) OK + - No Emacs freeze (re-entrance guard before child frame + dispatch, buffer switch with unbind_to on all paths) OK + - Focus restored to parent buffer after corfu closes + (childFrameCompletionActive flag + FOR_EACH_FRAME + visibility check + FocusedUIElementChanged) OK + - Non-completion child frames skipped (10KB buffer limit) OK + - specpdl_ref type used correctly (not ptrdiff_t) OK