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
This commit is contained in:
2026-02-28 18:01:38 +01:00
parent 6356cd9751
commit 4f37a8660e
2 changed files with 159 additions and 5 deletions

View File

@@ -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 <martin@sukany.cz>
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.

View File

@@ -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