New patch 0009 fixes O(L) line scanning in accessibilityLineForIndex:
and accessibilityRangeForLine: by adding a precomputed lineStartOffsets
array built once per cache rebuild. Queries go from O(L) linear scan
to O(log L) binary search.
README.txt: updated patch listing, text cache section, known limitations
(O(L) issue now resolved), stress test threshold raised to 50,000 lines.
Move file-scope statics (lastChildFrameBuffer/Modiff/Candidate) and
ns_ax_reset_accessibility_updating before announceChildFrameCompletion.
Using them before their declaration was a forward reference (UB in C).
Remove two spurious blank lines at start of announceChildFrameCompletion
method body.
New patch 0009 fixes HIGH severity issues from Opus review:
- Announcement coalescing (50ms debounce)
- cachedText retain+autorelease in accessibilityValue
- EmacsView dealloc: nil out emacsView on all AX elements
- Nil guards on protocol methods + overlayZoomActive
0007 updated: revert accidental em-dash→triple-dash, add overlayZoomActive nil guards
0008 updated: specpdl exception safety for accessibilityUpdating, lastChildFrameBuffer staticpro
Series now 9 patches total (0001-0006 unchanged, 0007-0009 new/updated).
BLOCKER #1: accessibilityUpdating flag exception safety.
A Lisp signal (longjmp) during postAccessibilityUpdates left
the re-entrance flag permanently YES, suppressing all future
AX notifications → VoiceOver goes silent randomly.
Fix: specpdl unwind protection (record_unwind_protect_ptr)
resets the flag on any longjmp. All 3 exit points use unbind_to.
BLOCKER #2: static struct buffer *lastBuffer dangling pointer.
Raw C pointer to buffer struct has no GC protection. After
kill-buffer, the pointer dangles.
Fix: file-scope Lisp_Object lastChildFrameBuffer with staticpro.
EQ comparison instead of pointer equality.
Also: revert accidental em-dash → triple-dash in title bar (0007),
fix README factual error (BUF_OVERLAY_MODIFF cache key).
hl-line-mode (and similar) bumps BUF_MODIFF via text property
changes on every cursor movement. The else-if structure caused
the modiff branch to fire (skipping ValueChanged correctly) but
also blocked the cursor-move branch (SelectedTextChanged).
Fix: use textDidChange flag to decouple the two branches.
ValueChanged and SelectedTextChanged remain mutually exclusive
for real edits, but SelectedTextChanged now fires when only
text properties changed.
- childFrameCompletionActive flag: set by child frame handler,
cleared when no child frame visible on parent's next cycle
- Posts FocusedUIElementChanged on parent buffer element to
restore VoiceOver character echo after corfu closes
- corfu-auto-delay: 1.0 → 2.0 (reduce popup noise)
Fbuffer_substring_no_properties operates on current_buffer, not the
passed buffer. Added set_buffer_internal_1 + record_unwind_current_buffer
with unbind_to on every return path.
Added: child frame buffer scanning for Corfu/Company-box.
Fixed: Zoom rect at text area left edge (not window center).
Added: 'selection' face match for company-tooltip-selection.
Zoom rect now at text area left edge (WINDOW_TEXT_TO_FRAME_PIXEL_X)
with cursor-width (FRAME_COLUMN_WIDTH) instead of full window width.
Face matching adds 'selection' (company-tooltip-selection).
Zoom now tracks the selected candidate row via overlayZoomRect
instead of always pointing at the text cursor. Returns to cursor
on typing (chars_modiff change).
Previous two-reference algorithm failed because:
- Vertico's cursor-space line (face=nil) confused the reference
- Count overlay processed before candidates overlay
- Group titles have distinct faces too
New approach: ns_ax_face_is_selected checks if face symbol name
contains 'current' or 'selected'. Works for all major frameworks
(Vertico, Icomplete, Ivy) without framework-specific code.
Key changes from previous version:
- Remove overlay text from ns_ax_buffer_text (was causing spurious
'new line' announcements via VoiceOver text diff)
- Do NOT invalidate text cache on overlay change
- Two-reference face detection (handles selected at any position)
- SDATA scan instead of per-char Faref for newline detection
- Zoom tracking via UAZoomChangeFocus for selected candidate row
- Deduplication via cachedCompletionAnnouncement
- Appends overlay before-string/after-string to AX text
- Detects BUF_OVERLAY_MODIFF changes
- Finds highlighted candidate via face text property
- Announces selected candidate via AnnouncementRequested
(fixes 'new line' instead of reading candidate)
Split VoiceOver accessibility into 4 logical patches:
0001: Base classes + text extraction (+753)
0002: Buffer/ModeLine/InteractiveSpan implementations (+1716)
0003: EmacsView integration + cursor tracking (+395)
0004: Documentation with known limitations (+75)
Each patch is self-contained: 0001 adds infrastructure that compiles
but doesn't change behavior. 0002 adds protocol implementations.
0003 wires everything into EmacsView. 0004 documents for users.
All patches verified: apply cleanly to current Emacs master,
final state identical to original monolithic patch.
Split the monolithic 3011-line patch into logical pieces:
0001: All new accessibility code (infrastructure, no existing code modified)
0002: EmacsView integration + cursor tracking (wiring only)
0003: Documentation (expanded with known limitations)
Improvements:
- Comprehensive commit messages with testing methodology
- Known limitations documented (text cap, bidi, mode-line icons)
- Documentation expanded with Known Limitations section
- Each patch is self-contained and reviewable
M1: accessibilityRangeForPosition uses specpdl unwind protection for
block_input/unblock_input (consistent with all other methods).
M2: Track BUF_OVERLAY_MODIFF in ensureTextCache — overlay-only changes
(timer-based completion highlight) now invalidate the text cache.
M3: Detect narrowing/widening by comparing cachedTextStart vs BUF_BEGV.
m1: Binary search (O(log n)) for visible runs in both
accessibilityIndexForCharpos and charposForAccessibilityIndex.
m3: Add EmacsAXSpanTypeNone = -1 to enum instead of (EmacsAXSpanType)-1 cast.
m5: Add TODO comment in ns_ax_mode_line_text about non-CHAR_GLYPH limitation.
README: Remove resolved overlay_modiff limitation, document binary search
and narrowing detection, update architecture section.
B1: setAccessibilityFocused: on EmacsAccessibilityBuffer now checks
![NSThread isMainThread] and dispatches to main via dispatch_async.
Prevents data race + AppKit thread violation from AX server thread.
W1: accessibilityInsertionPointLineNumber and accessibilityLineForIndex:
now use lineRangeForRange iteration — O(lines) instead of O(chars).
W2: ns_ax_scan_interactive_spans skips non-interactive regions using
Fnext_single_property_change for each scannable property and
Fnext_single_char_property_change for keymap overlays.
W3: ns_ax_event_is_line_nav_key inspects Vthis_command against known
navigation command symbols (next-line, previous-line, evil variants,
dired variants) instead of raw key codes. Tab/backtab fallback
retained via last_command_event.
W4: DEFSYM symbols renamed with ns_ax_ prefix (Qns_ax_button, etc.)
to avoid linker collisions with other Emacs source files.
Lisp symbol strings unchanged.
M3: Removed dead enum values (CheckBox, TextField, PopUpButton) and
corresponding dead switch cases.
M4: Improved accessibilityStyleRangeForIndex: comment documenting the
line-granularity simplification.
README: Updated stats, KNOWN LIMITATIONS, DEFSYM docs, test numbering.