The previous sed edit added a line but didn't update @@ hunk
headers, causing 'corrupt patch at line 2849'. Fixed:
- Main hunk: 2386 -> 2387 new-side lines
- Subsequent hunks: +1 offset on new-file line numbers
The function definition lacked a return type, causing:
nsterm.m:7149:1: error: a type specifier is required for all declarations
Added 'static BOOL' — the function returns YES/NO and is file-scoped.
Updated patch line counts in header and README accordingly.
B1: Fix memory leak in ns_ax_scan_interactive_spans — [spans copy]
returned +1 retained object never released by caller.
Now returns [[spans copy] autorelease].
B2: Remove dead function ns_ax_utf16_length_for_buffer_range —
defined but never called anywhere in the patch.
B3: Add specpdl unwind protection in
EmacsAccessibilityInteractiveSpan setAccessibilityFocused: —
if Fselect_window signals, block_input is now always matched
by unblock_input via record_unwind_protect_void.
W2: Document ns_ax_event_is_line_nav_key fragility in README
Known Limitations (raw keycodes vs command symbols).
W4: Add comment for #include intervals.h (TEXT_PROP_MEANS_INVISIBLE).
M3: accessibilityBoundsForRange: on EmacsView now delegates to the
focused EmacsAccessibilityBuffer for accurate per-range geometry,
with cursor-rect fallback for Zoom.
M4: Add <NSAccessibility> protocol conformance to
EmacsAccessibilityBuffer @interface declaration.
W1: Expanded commit message listing all new types, functions, DEFSYM
additions, and threading model.
8 methods were missing thread guards: accessibilityNumberOfCharacters,
accessibilitySelectedText, accessibilityStringForRange:,
accessibilityLineForIndex:, accessibilityRangeForLine:,
accessibilityRangeForIndex:, accessibilityVisibleCharacterRange,
setAccessibilitySelectedTextRange:.
All AX getters now consistently dispatch_sync to main thread.
GC safety comment added to InteractiveSpan setAccessibilityFocused:.
SelectedTextChanged → only for selection changes (mark active)
AnnouncementRequested → only for cursor moves (char/line)
Never both for the same event. Fixes double-speech globally.
- SelectedTextChanged posted only for focused element: prevents completion
buffer from triggering double-speech (old-candidate + new-candidate)
- AnnouncementRequested for char navigation restored (evil block cursor fix):
posted AFTER SelectedTextChanged so VoiceOver cancels its own reading
and uses our explicit char-at-point announcement
- Priority: Medium (was High)
- Remove static Lisp_Object locals; use DEFSYM in syms_of_nsterm (GC-safe)
- Replace Lisp calls in accessibilityIndexForCharpos / charposForAccessibilityIndex
with NSString composed-character traversal (thread-safe, no Lisp needed)
- isAccessibilityFocused reads cachedPoint instead of marker_position off-thread
- Remove double-announcement: character nav uses only SelectedTextChanged
- Line announcement priority: High → Medium (avoid suppressing VO feedback)
- Remove 'extern Lisp_Object last_command_event' - last_command_event
is a macro in globals.h (expands to globals.f_last_command_event),
so an extern declaration conflicts with the existing
'extern struct emacs_globals globals'
- Replace invalid C escape sequences '\C-n' and '\C-p' with
('n' & 0x1f) and ('p' & 0x1f) respectively
Root cause: ns_ax_buffer_text used BUF_BYTE_ADDRESS + raw pointer
read which crosses the buffer gap when visible runs span it. The gap
follows point, so completion cycling and dired navigation reliably
trigger corruption — VoiceOver reads wrong text.
Fix: Replace raw pointer extraction with Fbuffer_substring_no_properties
which handles the gap internally. Add ax_length field to visible run
struct for accurate UTF-16 length tracking (fixes supplementary
Unicode character offset drift).
Secondary: ax_offset accumulation now uses NSString length (UTF-16
units) instead of Emacs char count, preventing progressive drift in
index mapping for subsequent visible runs.
Major architectural change: ns_ax_buffer_text now skips text with
the 'invisible' property using TEXT_PROP_MEANS_INVISIBLE. Accessibility
text matches what the user sees on screen.
New ns_ax_visible_run struct tracks charpos↔ax-index mapping for each
visible text run. All 8 index conversion sites updated. Fixes wrong
line reading in dired, completions, and any buffer with invisible text.
Completions announcement now detects completions-highlight overlay
and reads the full highlighted candidate text instead of partial line.
2000-line patch, 54 references to new mapping infrastructure.
1. setAccessibilitySelectedTextRange: — enables bidirectional cursor
sync. Converts AX index to buffer charpos, moves point via
SET_PT_BOTH. VoiceOver can now place cursor in text interaction mode.
2. setAccessibilityFocused: — handles VO+Shift+Down interaction entry.
Ensures NS window focus, posts SelectedTextChanged so VoiceOver
reads current line.
3. Completions announcement — when point changes in a non-focused
buffer (e.g. *Completions* during Tab), posts
AnnouncementRequestedNotification with the current line text.
VoiceOver speaks the selected completion while minibuffer keeps focus.
P0 fixes (critical):
- accessibilityRangeForLine: include trailing newline — fixes
zero-length ranges for empty lines causing 'end of text'
- accessibilityVisibleCharacterRange: return full buffer range —
VoiceOver was treating visible window boundary as end of text
- ensureTextCache: invalidate when point moves outside cached region
(stale cachedTextStart after scrolling without editing)
- ns_ax_frame_for_range: use row->height not visible_height, clip
to text area — fixes VoiceOver cursor bleeding into adjacent lines
P1 fixes (important):
- Post SelectedTextChanged after FocusedUIElementChanged on focus
acquisition (windowDidBecomeKey) and window switch (C-x o)
- Post LayoutChangedNotification after tree rebuild
- accessibilityRangeForIndex: rangeOfComposedCharacterSequenceAtIndex
for emoji/combining marks
- Buffer accessibilityFrame excludes mode line height
- New elements init cachedPoint/cachedModiff=-1 to force first
notification
Reviewed by: architect + 3 reviewers (text, notifications, frames)