Compare commits

10 Commits

Author SHA1 Message Date
fe0a0181d3 patches: fix if(candidate) block indentation in 0008 (I1)
The if(candidate) block in postAccessibilityNotificationsForFrame:
had its opening brace at col 8 (tab) while the if itself is at
col 10 (tab+2), violating GNU coding style.  Two consecutive
closing braces at col 8 made nesting ambiguous on inspection.

Correct indentation (all +4 columns throughout the block):
  - if(candidate) brace: tab+4 = col 12
  - Body (/* Deduplicate */, if(![...]): tab+6 = col 14
  - Inner if(![...]) brace: 2 tabs = col 16
  - Inner if body: tab+tab+2 = col 18
  - Inner closing brace: 2 tabs = col 16
  - if(candidate) closing brace: tab+4 = col 12

New indentation exactly matches the original deleted code (same
method, before our refactor), confirming it follows the pre-existing
style in this file.
2026-03-04 13:53:05 +01:00
6dd3c9bfe4 patches: fix if(candidate) block indentation in 0008
The if(candidate) block's braces and body were not consistently
indented relative to the enclosing if(MINI_WINDOW_P) block.
Change context lines to deletion+addition pairs to correct:
- Opening brace: tab -> tab+4 (col 8 -> col 12)
- Body comment and inner if: tab+2 -> tab+6 (col 10 -> col 14)
- Inner if brace: tab+4 -> tab+6 (col 12 -> col 14)
- Inner and outer closing braces: tab+4->tab+6, tab->tab+4

This is a pre-existing indentation inconsistency that violates
GNU coding style; the if(candidate) check was at col 10 but its
brace was at col 8.
2026-03-04 13:50:45 +01:00
5f18d727e1 patches: fix em-dash and SPECPDL_INDEX ordering
Replace all Unicode em-dashes (---) with ASCII triple-dash (---)
across 0001-0007 (39 occurrences). GNU Emacs source files must be
ASCII; em-dash in comments would cause encoding issues on some systems.

Fix SPECPDL_INDEX() call order in postCompletionAnnouncementForBuffer:
(0003): specpdl_ref must be taken BEFORE block_input() so the snapshot
captures the pre-block_input specpdl depth. This matches the pattern
established in patches 0001 and 0002.
2026-03-04 13:38:54 +01:00
2b7c7abb48 patches: fix if-block indentation in 0008 (B5 fix cleanup)
The B5 goto-replacement introduced indentation inconsistency:
opening/closing braces were at tab+2, body at 6 spaces. The original
code (ns_ax_face_is_selected deletion) used tab for braces and tab+2
for body. Align the new if-block to match existing style:
- Opening brace: tab (was tab+2)
- Body lines (int selected_line, NSString *candidate, if(candidate)):
  tab+2 (was 6 spaces after partial fix)
- Continuation of NSString declaration: tab+4 (was tab)
- Closing brace: tab (was tab+2)
2026-03-04 13:37:39 +01:00
f51da9f8d4 patches: fix I2 body indent and I3 childFrameLastCandidate type
I2: Re-indent body of MINI_WINDOW_P if-block in 0008 from 6 spaces to
8 spaces, consistent with GNU style (if at 6 spaces, body at 8).

I3: Change childFrameLastCandidate from char* to NSString*.
Replaces xstrdup/xfree/strcmp with ObjC retain/release/isEqualToString:,
which is consistent with surrounding ObjC code style and avoids
heap allocation with xmalloc.
2026-03-04 13:35:54 +01:00
9ad01c03fd patches: re-fix B6 nlines < 128 -> 512 (lost in revert)
Fix was accidentally reverted when reverting the broken B5 goto fix.
Both line_starts[512]/line_ends[512] arrays and the main while loop
guard use 512; the trailing-line check must also use 512 to avoid
silently dropping the last line in buffers with 128-511 lines.
2026-03-04 13:32:24 +01:00
c418db05dc patches: fix all round-1 blockers (B1 B3 B5 B6)
B1 (0007): Fix dangling comment - change context line to deletion so
the opening comment of the removed ns_ax_face_is_selected function
is properly deleted, not left as an unclosed comment fragment that
would cause a compilation failure.

B3 (0002): Fix block_input/record_unwind_protect_void ordering in
two places in accessibilityRangeForPosition: and related method.
Correct order: block_input() BEFORE record_unwind_protect_void(),
so the unwind handler cannot call unblock_input without a matching
block_input even if specpdl_push fails.

B5 (0008): Replace goto skip_overlay_scan with proper if-block.
The goto skipped over ObjC variable declarations which is poor style
and would be flagged by Emacs maintainers.  Restructure as
if (MINI_WINDOW_P (w) && !didTextChange) { ... }.

B6 (0008): Fix nlines < 128 to nlines < 512 in ns_ax_selected_child_
frame_text.  Arrays line_starts[] and line_ends[] are declared with
size 512; the trailing-line guard used 128, silently dropping the
last line for buffers with 128-511 lines.
2026-03-04 13:29:55 +01:00
ea9d231177 Revert "patches: fix B5 - replace goto skip_overlay_scan with if-block"
This reverts commit a69aec96d8.
2026-03-04 13:26:20 +01:00
a69aec96d8 patches: fix B5 - replace goto skip_overlay_scan with if-block 2026-03-04 13:21:49 +01:00
97c14a3bd9 patches: fix all review issues from Opus core maintainer review
BLOCKERS fixed:
- #1: add missing skip_overlay_scan: label (goto without target)
- #2: move accessibilityLineForIndex: to patch 0002 (used in 0002/0003,
  was defined in 0007 — forward reference crash risk)
- #3: move BOOL singleLineMove to patch 0008 (declared in 0007, unused
  until 0008 — -Werror build failure)

WARNINGS fixed:
- #1: block_input ordering at 5 sites (record_unwind_protect_void must
  come AFTER block_input to avoid unmatched unblock_input on error)
- #2: deduplicate ns_ax_face_is_selected (move to patch 0001, remove
  from patch 0007 where it was duplicated)
- #4: improve childFrameLastBuffer comment (document rename edge case)
- #5: complete ChangeLog for patch 0007 (add 3 missing method entries)
- #6: document lastSelectedWindow/lastRootWindow GC safety in nsterm.h

MINOR fixed:
- #2: wrap ns_zoom_track_completion call in MAC_OS_X_VERSION >= 101000
  guard (consistent with the function definition)
- #3: @cindex Zoom verified present in VoiceOver section (already OK)
- #4: raise line_starts/line_ends bound from 128 to 512 (consistent
  with ns_ax_selected_overlay_text)
- #5: add echo_area_buffer[] lifetime comment referencing xdisp.c

All 9 patches apply cleanly (git apply verified sequentially).
2026-03-03 20:43:56 +01:00
8 changed files with 401 additions and 224 deletions

View File

@@ -1,4 +1,4 @@
From 488b91178be9a2dfd022533fce6b4adcd5c2ead0 Mon Sep 17 00:00:00 2001 From 29546d323559dbbefd846f7b2720285ff90368c8 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 2/9] ns: add accessibility base classes and text extraction Subject: [PATCH 2/9] ns: add accessibility base classes and text extraction
@@ -28,9 +28,9 @@ rect via glyph matrix.
ns-accessibility-enabled with corrected doc: initial value is nil, ns-accessibility-enabled with corrected doc: initial value is nil,
set non-nil automatically when an AT is detected at startup. set non-nil automatically when an AT is detected at startup.
--- ---
src/nsterm.h | 131 +++++++++++++++ src/nsterm.h | 131 ++++++++++++++
src/nsterm.m | 454 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 482 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 585 insertions(+) 2 files changed, 613 insertions(+)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index ea6e7ba4f5..f245675513 100644 index ea6e7ba4f5..f245675513 100644
@@ -52,11 +52,11 @@ index ea6e7ba4f5..f245675513 100644
+/* Base class for virtual accessibility elements attached to EmacsView. */ +/* Base class for virtual accessibility elements attached to EmacsView. */
+@interface EmacsAccessibilityElement : NSAccessibilityElement +@interface EmacsAccessibilityElement : NSAccessibilityElement
+@property (nonatomic, unsafe_unretained) EmacsView *emacsView; +@property (nonatomic, unsafe_unretained) EmacsView *emacsView;
+/* Lisp window object safe across GC cycles. +/* Lisp window object --- safe across GC cycles.
+ GC safety: these Lisp_Objects are NOT visible to GC via staticpro + GC safety: these Lisp_Objects are NOT visible to GC via staticpro
+ or the specpdl stack. This is safe because: + or the specpdl stack. This is safe because:
+ (1) Emacs GC runs only on the main thread, at well-defined safe + (1) Emacs GC runs only on the main thread, at well-defined safe
+ points during Lisp evaluation never during redisplay. + points during Lisp evaluation --- never during redisplay.
+ (2) Accessibility elements are owned by EmacsView which belongs to + (2) Accessibility elements are owned by EmacsView which belongs to
+ an active frame; windows referenced here are always reachable + an active frame; windows referenced here are always reachable
+ from the frame's window tree until rebuildAccessibilityTree + from the frame's window tree until rebuildAccessibilityTree
@@ -81,7 +81,7 @@ index ea6e7ba4f5..f245675513 100644
+ NSUInteger ax_length; /* Length in accessibility string (UTF-16 units). */ + NSUInteger ax_length; /* Length in accessibility string (UTF-16 units). */
+} ns_ax_visible_run; +} ns_ax_visible_run;
+ +
+/* Virtual AXTextArea element one per visible Emacs window (buffer). */ +/* Virtual AXTextArea element --- one per visible Emacs window (buffer). */
+@interface EmacsAccessibilityBuffer +@interface EmacsAccessibilityBuffer
+ : EmacsAccessibilityElement <NSAccessibility> + : EmacsAccessibilityElement <NSAccessibility>
+{ +{
@@ -119,7 +119,7 @@ index ea6e7ba4f5..f245675513 100644
+- (void)invalidateInteractiveSpans; +- (void)invalidateInteractiveSpans;
+@end +@end
+ +
+/* Virtual AXStaticText element one per mode line. */ +/* Virtual AXStaticText element --- one per mode line. */
+@interface EmacsAccessibilityModeLine : EmacsAccessibilityElement +@interface EmacsAccessibilityModeLine : EmacsAccessibilityElement
+@end +@end
+ +
@@ -189,7 +189,7 @@ index ea6e7ba4f5..f245675513 100644
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 88c9251c18..9d36de66f9 100644 index 88c9251c18..3b923ee5fa 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch) @@ -46,6 +46,7 @@ Updated by Christian Limpach (chris@nice.ch)
@@ -200,7 +200,7 @@ index 88c9251c18..9d36de66f9 100644
#include "systime.h" #include "systime.h"
#include "character.h" #include "character.h"
#include "xwidget.h" #include "xwidget.h"
@@ -7201,6 +7202,432 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg @@ -7201,6 +7202,460 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
} }
#endif #endif
@@ -249,8 +249,11 @@ index 88c9251c18..9d36de66f9 100644
+ +
+ specpdl_ref count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_current_buffer (); + record_unwind_current_buffer ();
+ record_unwind_protect_void (unblock_input); + /* block_input must come before record_unwind_protect_void (unblock_input):
+ if specpdl_push were to fail after registration, the unwind handler
+ would call unblock_input without a matching block_input. */
+ block_input (); + block_input ();
+ record_unwind_protect_void (unblock_input);
+ if (b != current_buffer) + if (b != current_buffer)
+ set_buffer_internal_1 (b); + set_buffer_internal_1 (b);
+ +
@@ -291,7 +294,7 @@ index 88c9251c18..9d36de66f9 100644
+ +
+ /* Extract this visible run's text. Use + /* Extract this visible run's text. Use
+ Fbuffer_substring_no_properties which correctly handles the + Fbuffer_substring_no_properties which correctly handles the
+ buffer gap raw BUF_BYTE_ADDRESS reads across the gap would + buffer gap --- raw BUF_BYTE_ADDRESS reads across the gap would
+ include garbage bytes when the run spans the gap position. */ + include garbage bytes when the run spans the gap position. */
+ Lisp_Object lstr = Fbuffer_substring_no_properties ( + Lisp_Object lstr = Fbuffer_substring_no_properties (
+ make_fixnum (pos), make_fixnum (run_end)); + make_fixnum (pos), make_fixnum (run_end));
@@ -372,7 +375,7 @@ index 88c9251c18..9d36de66f9 100644
+ return NSZeroRect; + return NSZeroRect;
+ +
+ /* charpos_start and charpos_len are already in buffer charpos + /* charpos_start and charpos_len are already in buffer charpos
+ space the caller maps AX string indices through + space --- the caller maps AX string indices through
+ charposForAccessibilityIndex which handles invisible text. */ + charposForAccessibilityIndex which handles invisible text. */
+ ptrdiff_t cp_start = charpos_start; + ptrdiff_t cp_start = charpos_start;
+ ptrdiff_t cp_end = cp_start + charpos_len; + ptrdiff_t cp_end = cp_start + charpos_len;
@@ -553,6 +556,31 @@ index 88c9251c18..9d36de66f9 100644
+ Deferring via dispatch_async lets the current method return first, + Deferring via dispatch_async lets the current method return first,
+ freeing the main queue for VoiceOver's dispatch_sync calls. */ + freeing the main queue for VoiceOver's dispatch_sync calls. */
+ +
+/* Return true if FACE (a symbol or list of symbols) looks like a
+ "selected item" face. Substring match is intentionally broad ---
+ it catches vertico-current, icomplete-selected-match,
+ ivy-current-match, company-tooltip-selection, and similar.
+ False positives are harmless: this runs only on overlay/child-frame
+ strings during completion, never in a hot redisplay path. */
+static bool
+ns_ax_face_is_selected (Lisp_Object face)
+{
+ if (SYMBOLP (face) && !NILP (face))
+ {
+ const char *name = SSDATA (SYMBOL_NAME (face));
+ if (strstr (name, "current") || strstr (name, "selected")
+ || strstr (name, "selection"))
+ return true;
+ }
+ if (CONSP (face))
+ {
+ for (Lisp_Object tail = face; CONSP (tail); tail = XCDR (tail))
+ if (ns_ax_face_is_selected (XCAR (tail)))
+ return true;
+ }
+ return false;
+}
+
+static inline void +static inline void
+ns_ax_post_notification (id element, +ns_ax_post_notification (id element,
+ NSAccessibilityNotificationName name) + NSAccessibilityNotificationName name)
@@ -633,7 +661,7 @@ index 88c9251c18..9d36de66f9 100644
/* ========================================================================== /* ==========================================================================
EmacsView implementation EmacsView implementation
@@ -11657,6 +12084,24 @@ Convert an X font name (XLFD) to an NS font name. @@ -11657,6 +12112,24 @@ Convert an X font name (XLFD) to an NS font name.
DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic"); DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion"); DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");
@@ -658,7 +686,7 @@ index 88c9251c18..9d36de66f9 100644
Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier)); Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier)); Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier));
Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier)); Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
@@ -11805,6 +12250,15 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with @@ -11805,6 +12278,15 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
This variable is ignored on Mac OS X < 10.7 and GNUstep. */); This variable is ignored on Mac OS X < 10.7 and GNUstep. */);
ns_use_srgb_colorspace = YES; ns_use_srgb_colorspace = YES;

View File

@@ -1,4 +1,4 @@
From ba093f98d3bb1281278170f01f0792c2b24cf94f Mon Sep 17 00:00:00 2001 From f587654717e7a3d3121e4871f04ffbf4e0d5e9be Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 3/9] ns: implement buffer accessibility element (core Subject: [PATCH 3/9] ns: implement buffer accessibility element (core
@@ -20,20 +20,23 @@ loop is safe: it runs only on actual character modifications.
(accessibilityRole, accessibilityLabel, accessibilityValue) (accessibilityRole, accessibilityLabel, accessibilityValue)
(accessibilityNumberOfCharacters, accessibilitySelectedText) (accessibilityNumberOfCharacters, accessibilitySelectedText)
(accessibilitySelectedTextRange, accessibilityInsertionPointLineNumber) (accessibilitySelectedTextRange, accessibilityInsertionPointLineNumber)
(accessibilityLineForIndex:): New method; return the line number for an
AX character index; defined here so patches 0003+ can call it without
forward reference.
(accessibilityRangeForLine:, accessibilityRangeForIndex:) (accessibilityRangeForLine:, accessibilityRangeForIndex:)
(accessibilityStyleRangeForIndex:, accessibilityFrameForRange:) (accessibilityStyleRangeForIndex:, accessibilityFrameForRange:)
(accessibilityRangeForPosition:, accessibilityVisibleCharacterRange) (accessibilityRangeForPosition:, accessibilityVisibleCharacterRange)
(accessibilityFrame, setAccessibilitySelectedTextRange:) (accessibilityFrame, setAccessibilitySelectedTextRange:)
(setAccessibilityFocused:): Implement NSAccessibility protocol methods. (setAccessibilityFocused:): Implement NSAccessibility protocol methods.
--- ---
src/nsterm.m | 1115 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 1135 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1115 insertions(+) 1 file changed, 1135 insertions(+)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 9d36de66f9..6256dbc22e 100644 index 3b923ee5fa..41c6b8dc14 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7625,6 +7625,1121 @@ - (id)accessibilityTopLevelUIElement @@ -7653,6 +7653,1141 @@ - (id)accessibilityTopLevelUIElement
@end @end
@@ -203,8 +206,8 @@ index 9d36de66f9..6256dbc22e 100644
+ /* Block input to prevent concurrent redisplay from modifying buffer + /* Block input to prevent concurrent redisplay from modifying buffer
+ state while we read text properties. Unwind-protected so + state while we read text properties. Unwind-protected so
+ block_input is always matched by unblock_input on signal. */ + block_input is always matched by unblock_input on signal. */
+ record_unwind_protect_void (unblock_input);
+ block_input (); + block_input ();
+ record_unwind_protect_void (unblock_input);
+ if (b != current_buffer) + if (b != current_buffer)
+ set_buffer_internal_1 (b); + set_buffer_internal_1 (b);
+ +
@@ -355,7 +358,7 @@ index 9d36de66f9..6256dbc22e 100644
+ NSTRACE ("EmacsAccessibilityBuffer ensureTextCache"); + NSTRACE ("EmacsAccessibilityBuffer ensureTextCache");
+ /* This method is only called from the main thread (AX getters + /* This method is only called from the main thread (AX getters
+ dispatch_sync to main first). Reads of cachedText/cachedTextModiff + dispatch_sync to main first). Reads of cachedText/cachedTextModiff
+ below are therefore safe without @synchronized only the + below are therefore safe without @synchronized --- only the
+ write section at the end needs synchronization to protect + write section at the end needs synchronization to protect
+ against concurrent reads from AX server thread. */ + against concurrent reads from AX server thread. */
+ eassert ([NSThread isMainThread]); + eassert ([NSThread isMainThread]);
@@ -470,7 +473,7 @@ index 9d36de66f9..6256dbc22e 100644
+ /* Binary search: runs are sorted by charpos (ascending). Find the + /* Binary search: runs are sorted by charpos (ascending). Find the
+ run whose [charpos, charpos+length) range contains the target, + run whose [charpos, charpos+length) range contains the target,
+ or the nearest run after an invisible gap. O(log n) instead of + or the nearest run after an invisible gap. O(log n) instead of
+ O(n) matters for org-mode with many folded sections. */ + O(n) --- matters for org-mode with many folded sections. */
+ NSUInteger lo = 0, hi = visibleRunCount; + NSUInteger lo = 0, hi = visibleRunCount;
+ while (lo < hi) + while (lo < hi)
+ { + {
@@ -519,10 +522,10 @@ index 9d36de66f9..6256dbc22e 100644
+ +
+/* Convert accessibility string index to buffer charpos. +/* Convert accessibility string index to buffer charpos.
+ Safe to call from any thread: uses only cachedText (NSString) and + Safe to call from any thread: uses only cachedText (NSString) and
+ visibleRuns no Lisp calls. */ + visibleRuns --- no Lisp calls. */
+- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx +- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx
+{ +{
+ /* May be called from AX server thread synchronize. */ + /* May be called from AX server thread --- synchronize. */
+ @synchronized (self) + @synchronized (self)
+ { + {
+ if (visibleRunCount == 0) + if (visibleRunCount == 0)
@@ -564,7 +567,7 @@ index 9d36de66f9..6256dbc22e 100644
+ return cp; + return cp;
+ } + }
+ } + }
+ /* Past end return last charpos. */ + /* Past end --- return last charpos. */
+ if (lo > 0) + if (lo > 0)
+ { + {
+ ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; + ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1];
@@ -586,7 +589,7 @@ index 9d36de66f9..6256dbc22e 100644
+ deadlocking the AX server thread. This is prevented by: + deadlocking the AX server thread. This is prevented by:
+ +
+ 1. validWindow checks WINDOW_LIVE_P and BUFFERP before every + 1. validWindow checks WINDOW_LIVE_P and BUFFERP before every
+ Lisp access the window and buffer are verified live. + Lisp access --- the window and buffer are verified live.
+ 2. All dispatch_sync blocks run on the main thread where no + 2. All dispatch_sync blocks run on the main thread where no
+ concurrent Lisp code can modify state between checks. + concurrent Lisp code can modify state between checks.
+ 3. block_input prevents timer events and process output from + 3. block_input prevents timer events and process output from
@@ -816,10 +819,9 @@ index 9d36de66f9..6256dbc22e 100644
+ +
+ specpdl_ref count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_current_buffer (); + record_unwind_current_buffer ();
+ /* Ensure block_input is always matched by unblock_input even if + /* block_input must come before record_unwind_protect_void (unblock_input). */
+ Fset_marker or another Lisp call signals (longjmp). */
+ record_unwind_protect_void (unblock_input);
+ block_input (); + block_input ();
+ record_unwind_protect_void (unblock_input);
+ +
+ /* Convert accessibility index to buffer charpos via mapping. */ + /* Convert accessibility index to buffer charpos via mapping. */
+ ptrdiff_t charpos = [self charposForAccessibilityIndex:range.location]; + ptrdiff_t charpos = [self charposForAccessibilityIndex:range.location];
@@ -873,10 +875,10 @@ index 9d36de66f9..6256dbc22e 100644
+ if (!view || !view->emacsframe) + if (!view || !view->emacsframe)
+ return; + return;
+ +
+ /* Use specpdl unwind protection for block_input safety. */ + /* block_input must come before record_unwind_protect_void (unblock_input). */
+ specpdl_ref count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_protect_void (unblock_input);
+ block_input (); + block_input ();
+ record_unwind_protect_void (unblock_input);
+ +
+ /* Select the Emacs window so keyboard focus follows VoiceOver. */ + /* Select the Emacs window so keyboard focus follows VoiceOver. */
+ struct frame *f = view->emacsframe; + struct frame *f = view->emacsframe;
@@ -954,6 +956,27 @@ index 9d36de66f9..6256dbc22e 100644
+ return [self rangeForLine:(NSUInteger)line textLength:len]; + return [self rangeForLine:(NSUInteger)line textLength:len];
+} +}
+ +
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index
+{
+ if (![NSThread isMainThread])
+ {
+ __block NSInteger result;
+ dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityLineForIndex:index];
+ });
+ return result;
+ }
+ [self ensureTextCache];
+ if (!cachedText || index < 0)
+ return 0;
+
+ NSUInteger idx = (NSUInteger) index;
+ if (idx > [cachedText length])
+ idx = [cachedText length];
+
+ return [self lineForAXIndex:idx];
+}
+
+- (NSRange)accessibilityRangeForIndex:(NSInteger)index +- (NSRange)accessibilityRangeForIndex:(NSInteger)index
+{ +{
+ if (![NSThread isMainThread]) + if (![NSThread isMainThread])
@@ -1037,8 +1060,8 @@ index 9d36de66f9..6256dbc22e 100644
+ so block_input is always matched by unblock_input, even if + so block_input is always matched by unblock_input, even if
+ ensureTextCache triggers a Lisp signal (longjmp). */ + ensureTextCache triggers a Lisp signal (longjmp). */
+ specpdl_ref count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_protect_void (unblock_input);
+ block_input (); + block_input ();
+ record_unwind_protect_void (unblock_input);
+ +
+ /* Find the glyph row at this y coordinate. */ + /* Find the glyph row at this y coordinate. */
+ struct glyph_matrix *matrix = w->current_matrix; + struct glyph_matrix *matrix = w->current_matrix;

View File

@@ -1,4 +1,4 @@
From 0566d2cf3bc1b6309b3b3dd1a048bac7c63937e9 Mon Sep 17 00:00:00 2001 From d8a98fc40d8285c19e0a73a7e8a53778926b9836 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 4/9] ns: add buffer notification dispatch and mode-line Subject: [PATCH 4/9] ns: add buffer notification dispatch and mode-line
@@ -26,17 +26,17 @@ mode line.
1 file changed, 606 insertions(+) 1 file changed, 606 insertions(+)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 6256dbc22e..9e0e317237 100644 index 41c6b8dc14..16343f978a 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -8740,6 +8740,612 @@ - (NSRect)accessibilityFrame @@ -8788,6 +8788,612 @@ - (NSRect)accessibilityFrame
@end @end
+ +
+ +
+/* =================================================================== +/* ===================================================================
+ EmacsAccessibilityBuffer (Notifications) AX event dispatch + EmacsAccessibilityBuffer (Notifications) --- AX event dispatch
+ +
+ These methods notify VoiceOver of text and selection changes. + These methods notify VoiceOver of text and selection changes.
+ Called from the redisplay cycle (postAccessibilityUpdates). + Called from the redisplay cycle (postAccessibilityUpdates).
@@ -51,7 +51,7 @@ index 6256dbc22e..9e0e317237 100644
+ if (point > self.cachedPoint + if (point > self.cachedPoint
+ && point - self.cachedPoint == 1) + && point - self.cachedPoint == 1)
+ { + {
+ /* Single char inserted refresh cache and grab it. */ + /* Single char inserted --- refresh cache and grab it. */
+ [self invalidateTextCache]; + [self invalidateTextCache];
+ [self ensureTextCache]; + [self ensureTextCache];
+ if (cachedText) + if (cachedText)
@@ -70,7 +70,7 @@ index 6256dbc22e..9e0e317237 100644
+ /* Update cachedPoint here so the selection-move branch does NOT + /* Update cachedPoint here so the selection-move branch does NOT
+ fire for point changes caused by edits. WebKit and Chromium + fire for point changes caused by edits. WebKit and Chromium
+ never send both ValueChanged and SelectedTextChanged for the + never send both ValueChanged and SelectedTextChanged for the
+ same user action they are mutually exclusive. */ + same user action --- they are mutually exclusive. */
+ self.cachedPoint = point; + self.cachedPoint = point;
+ +
+ NSDictionary *change = @{ + NSDictionary *change = @{
@@ -281,8 +281,8 @@ index 6256dbc22e..9e0e317237 100644
+ ptrdiff_t currentOverlayStart = 0; + ptrdiff_t currentOverlayStart = 0;
+ ptrdiff_t currentOverlayEnd = 0; + ptrdiff_t currentOverlayEnd = 0;
+ +
+ block_input ();
+ specpdl_ref count2 = SPECPDL_INDEX (); + specpdl_ref count2 = SPECPDL_INDEX ();
+ block_input ();
+ record_unwind_protect_void (unblock_input); + record_unwind_protect_void (unblock_input);
+ record_unwind_current_buffer (); + record_unwind_current_buffer ();
+ if (b != current_buffer) + if (b != current_buffer)
@@ -471,7 +471,7 @@ index 6256dbc22e..9e0e317237 100644
+ } + }
+ +
+ /* --- Cursor moved or selection changed --- + /* --- Cursor moved or selection changed ---
+ Use 'else if' edits and selection moves are mutually exclusive + Use 'else if' --- edits and selection moves are mutually exclusive
+ per the WebKit/Chromium pattern. */ + per the WebKit/Chromium pattern. */
+ else if (point != self.cachedPoint || markActive != self.cachedMarkActive) + else if (point != self.cachedPoint || markActive != self.cachedMarkActive)
+ { + {

View File

@@ -1,4 +1,4 @@
From ce123c5b0c25467dd6fb6d4a2aeda59687fadefc Mon Sep 17 00:00:00 2001 From 9c233aa400c2769e1621ec37f326d1e24c0da2df Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 5/9] ns: add interactive span elements for Tab navigation Subject: [PATCH 5/9] ns: add interactive span elements for Tab navigation
@@ -14,14 +14,31 @@ elements with an AXPress action that sends a synthetic TAB keystroke.
(accessibilityChildrenInNavigationOrder): Return cached span array, (accessibilityChildrenInNavigationOrder): Return cached span array,
rebuilding lazily when interactiveSpansDirty is set. rebuilding lazily when interactiveSpansDirty is set.
--- ---
src/nsterm.m | 293 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 293 insertions(+) 1 file changed, 298 insertions(+), 4 deletions(-)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 9e0e317237..d65609cc79 100644 index 16343f978a..f5e5cea074 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -9346,6 +9346,299 @@ - (NSRect)accessibilityFrame @@ -8669,12 +8669,12 @@ - (NSRange)accessibilityRangeForPosition:(NSPoint)screenPoint
return NSMakeRange (0, 0);
/* Block input to prevent concurrent redisplay from modifying the
- glyph matrix while we traverse it. Use specpdl unwind protection
- so block_input is always matched by unblock_input, even if
- ensureTextCache triggers a Lisp signal (longjmp). */
+ glyph matrix while we traverse it. block_input must come before
+ record_unwind_protect_void (unblock_input) so that the unwind
+ handler is never called without a matching block_input. */
specpdl_ref count = SPECPDL_INDEX ();
- record_unwind_protect_void (unblock_input);
block_input ();
+ record_unwind_protect_void (unblock_input);
/* Find the glyph row at this y coordinate. */
struct glyph_matrix *matrix = w->current_matrix;
@@ -9394,6 +9394,300 @@ - (NSRect)accessibilityFrame
@end @end
@@ -248,11 +265,12 @@ index 9e0e317237..d65609cc79 100644
+ window being deleted between capture and execution. */ + window being deleted between capture and execution. */
+ if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin))) + if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
+ return; + return;
+ /* Use specpdl unwind protection so that block_input is always + /* block_input must come before record_unwind_protect_void (unblock_input)
+ matched by unblock_input, even if Fselect_window signals. */ + so the unwind handler is never invoked without a matching block_input,
+ even if Fselect_window signals (longjmp). */
+ specpdl_ref count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX ();
+ record_unwind_protect_void (unblock_input);
+ block_input (); + block_input ();
+ record_unwind_protect_void (unblock_input);
+ record_unwind_current_buffer (); + record_unwind_current_buffer ();
+ Fselect_window (lwin, Qnil); + Fselect_window (lwin, Qnil);
+ struct window *w = XWINDOW (lwin); + struct window *w = XWINDOW (lwin);

View File

@@ -1,4 +1,4 @@
From 3f894218b771f2aa098f19dfb4bdc8b13408c8c8 Mon Sep 17 00:00:00 2001 From 411c0c3f06ad4c2d5aae2b17b809e8899ea892ba Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 6/9] ns: integrate accessibility with EmacsView and redisplay Subject: [PATCH 6/9] ns: integrate accessibility with EmacsView and redisplay
@@ -23,8 +23,9 @@ com.apple.accessibility.api distributed notification.
(accessibilityAttributeValue:forParameter:): New methods. (accessibilityAttributeValue:forParameter:): New methods.
--- ---
etc/NEWS | 13 ++ etc/NEWS | 13 ++
src/nsterm.h | 7 +-
src/nsterm.m | 474 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/nsterm.m | 474 +++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 477 insertions(+), 10 deletions(-) 3 files changed, 483 insertions(+), 11 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS diff --git a/etc/NEWS b/etc/NEWS
index 4c149e41d6..7f917f93b2 100644 index 4c149e41d6..7f917f93b2 100644
@@ -50,8 +51,26 @@ index 4c149e41d6..7f917f93b2 100644
--- ---
** Re-introduced dictation, lost in Emacs v30 (macOS). ** Re-introduced dictation, lost in Emacs v30 (macOS).
We lost macOS dictation in v30 when migrating to NSTextInputClient. We lost macOS dictation in v30 when migrating to NSTextInputClient.
diff --git a/src/nsterm.h b/src/nsterm.h
index f245675513..4bf79a9adb 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -590,7 +590,12 @@ typedef NS_ENUM(NSInteger, EmacsAXSpanType)
char *old_title;
BOOL maximizing_resize;
NSMutableArray *accessibilityElements;
- /* See GC safety comment on EmacsAccessibilityElement.lispWindow. */
+ /* Lisp_Object ivars not visible to GC. Both objects are always
+ reachable via the frame's live window tree, so GC cannot collect
+ them. After a window-tree rebuild (delete-window, split-window)
+ a stale EQ match would merely skip a focus notification --- the
+ worst case is one spurious VoiceOver focus event per rebuild.
+ No staticpro() needed: the window tree holds a strong reference. */
Lisp_Object lastSelectedWindow;
Lisp_Object lastRootWindow;
BOOL accessibilityTreeValid;
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index d65609cc79..4ba9b41b3b 100644 index f5e5cea074..c3cd83b774 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1393,7 +1393,8 @@ so the visual offset is (ov_line + 1) * line_h from @@ -1393,7 +1393,8 @@ so the visual offset is (ov_line + 1) * line_h from
@@ -131,7 +150,7 @@ index d65609cc79..4ba9b41b3b 100644
- (void)antialiasThresholdDidChange:(NSNotification *)notification - (void)antialiasThresholdDidChange:(NSNotification *)notification
{ {
#ifdef NS_IMPL_COCOA #ifdef NS_IMPL_COCOA
@@ -7628,7 +7679,6 @@ - (id)accessibilityTopLevelUIElement @@ -7656,7 +7707,6 @@ - (id)accessibilityTopLevelUIElement
@@ -139,15 +158,15 @@ index d65609cc79..4ba9b41b3b 100644
static BOOL static BOOL
ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point, ns_ax_find_completion_overlay_range (struct buffer *b, ptrdiff_t point,
ptrdiff_t *out_start, ptrdiff_t *out_start,
@@ -8741,7 +8791,6 @@ - (NSRect)accessibilityFrame @@ -8789,7 +8839,6 @@ - (NSRect)accessibilityFrame
@end @end
- -
/* =================================================================== /* ===================================================================
EmacsAccessibilityBuffer (Notifications) AX event dispatch EmacsAccessibilityBuffer (Notifications) --- AX event dispatch
@@ -9235,6 +9284,54 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9283,6 +9332,54 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
granularity = ns_ax_text_selection_granularity_line; granularity = ns_ax_text_selection_granularity_line;
} }
@@ -202,7 +221,7 @@ index d65609cc79..4ba9b41b3b 100644
/* Post notifications for focused and non-focused elements. */ /* Post notifications for focused and non-focused elements. */
if ([self isAccessibilityFocused]) if ([self isAccessibilityFocused])
[self postFocusedCursorNotification:point [self postFocusedCursorNotification:point
@@ -9347,7 +9444,6 @@ - (NSRect)accessibilityFrame @@ -9395,7 +9492,6 @@ - (NSRect)accessibilityFrame
@end @end
@@ -210,7 +229,7 @@ index d65609cc79..4ba9b41b3b 100644
/* =================================================================== /* ===================================================================
EmacsAccessibilityInteractiveSpan --- helpers and implementation EmacsAccessibilityInteractiveSpan --- helpers and implementation
=================================================================== */ =================================================================== */
@@ -9684,6 +9780,7 @@ - (void)dealloc @@ -9733,6 +9829,7 @@ - (void)dealloc
[layer release]; [layer release];
#endif #endif
@@ -218,7 +237,7 @@ index d65609cc79..4ba9b41b3b 100644
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -11032,6 +11129,32 @@ - (void)windowDidBecomeKey /* for direct calls */ @@ -11081,6 +11178,32 @@ - (void)windowDidBecomeKey /* for direct calls */
XSETFRAME (event.frame_or_window, emacsframe); XSETFRAME (event.frame_or_window, emacsframe);
kbd_buffer_store_event (&event); kbd_buffer_store_event (&event);
ns_send_appdefined (-1); // Kick main loop ns_send_appdefined (-1); // Kick main loop
@@ -251,7 +270,7 @@ index d65609cc79..4ba9b41b3b 100644
} }
@@ -12269,6 +12392,332 @@ - (int) fullscreenState @@ -12318,6 +12441,332 @@ - (int) fullscreenState
return fs_state; return fs_state;
} }
@@ -271,7 +290,7 @@ index d65609cc79..4ba9b41b3b 100644
+ +
+ if (WINDOW_LEAF_P (w)) + if (WINDOW_LEAF_P (w))
+ { + {
+ /* Buffer element reuse existing if available. */ + /* Buffer element --- reuse existing if available. */
+ EmacsAccessibilityBuffer *elem + EmacsAccessibilityBuffer *elem
+ = [existing objectForKey:[NSValue valueWithPointer:w]]; + = [existing objectForKey:[NSValue valueWithPointer:w]];
+ if (!elem) + if (!elem)
@@ -305,7 +324,7 @@ index d65609cc79..4ba9b41b3b 100644
+ } + }
+ else + else
+ { + {
+ /* Internal (combination) window recurse into children. */ + /* Internal (combination) window --- recurse into children. */
+ Lisp_Object child = w->contents; + Lisp_Object child = w->contents;
+ while (!NILP (child)) + while (!NILP (child))
+ { + {
@@ -417,7 +436,7 @@ index d65609cc79..4ba9b41b3b 100644
+ accessibilityUpdating = YES; + accessibilityUpdating = YES;
+ +
+ /* Detect window tree change (split, delete, new buffer). Compare + /* Detect window tree change (split, delete, new buffer). Compare
+ FRAME_ROOT_WINDOW if it changed, the tree structure changed. */ + FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */
+ Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe); + Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
+ if (!EQ (curRoot, lastRootWindow)) + if (!EQ (curRoot, lastRootWindow))
+ { + {
@@ -426,12 +445,12 @@ index d65609cc79..4ba9b41b3b 100644
+ } + }
+ +
+ /* If tree is stale, rebuild FIRST so we don't iterate freed + /* If tree is stale, rebuild FIRST so we don't iterate freed
+ window pointers. Skip notifications for this cycle the + window pointers. Skip notifications for this cycle --- the
+ freshly-built elements have no previous state to diff against. */ + freshly-built elements have no previous state to diff against. */
+ if (!accessibilityTreeValid) + if (!accessibilityTreeValid)
+ { + {
+ [self rebuildAccessibilityTree]; + [self rebuildAccessibilityTree];
+ /* Invalidate span cache window layout changed. */ + /* Invalidate span cache --- window layout changed. */
+ for (EmacsAccessibilityElement *elem in accessibilityElements) + for (EmacsAccessibilityElement *elem in accessibilityElements)
+ if ([elem isKindOfClass: [EmacsAccessibilityBuffer class]]) + if ([elem isKindOfClass: [EmacsAccessibilityBuffer class]])
+ [(EmacsAccessibilityBuffer *) elem invalidateInteractiveSpans]; + [(EmacsAccessibilityBuffer *) elem invalidateInteractiveSpans];
@@ -584,7 +603,7 @@ index d65609cc79..4ba9b41b3b 100644
@end /* EmacsView */ @end /* EmacsView */
@@ -14265,12 +14714,17 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with @@ -14314,12 +14763,17 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
ns_use_srgb_colorspace = YES; ns_use_srgb_colorspace = YES;
DEFVAR_BOOL ("ns-accessibility-enabled", ns_accessibility_enabled, DEFVAR_BOOL ("ns-accessibility-enabled", ns_accessibility_enabled,

View File

@@ -1,4 +1,4 @@
From 23139d3e63a0d97cf1fdf0421fd7c41acce0bd6b Mon Sep 17 00:00:00 2001 From 274c545be1a3af3c7e6f416ac3a22e3b98626b0b Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 7/9] doc: add VoiceOver accessibility section to macOS Subject: [PATCH 7/9] doc: add VoiceOver accessibility section to macOS
@@ -111,10 +111,10 @@ index 6bd334f48e..72ac3a9aa9 100644
@section GNUstep Support @section GNUstep Support
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 4ba9b41b3b..a0419bb5df 100644 index c3cd83b774..e4e43dd7a3 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -14715,9 +14715,13 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with @@ -14764,9 +14764,13 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
DEFVAR_BOOL ("ns-accessibility-enabled", ns_accessibility_enabled, DEFVAR_BOOL ("ns-accessibility-enabled", ns_accessibility_enabled,
doc: /* Non-nil enables Zoom cursor tracking and VoiceOver support. doc: /* Non-nil enables Zoom cursor tracking and VoiceOver support.

View File

@@ -1,4 +1,4 @@
From 217177caefc709c37ae04732ec595ace903e4cc4 Mon Sep 17 00:00:00 2001 From b87fb2b1824761fe3d91a27afe966eada39c1c45 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Mon, 2 Mar 2026 18:39:46 +0100 Date: Mon, 2 Mar 2026 18:39:46 +0100
Subject: [PATCH 8/9] ns: announce overlay completion candidates for VoiceOver Subject: [PATCH 8/9] ns: announce overlay completion candidates for VoiceOver
@@ -7,10 +7,13 @@ Completion frameworks such as Vertico, Ivy, and Icomplete render
candidates via overlay before-string/after-string properties. Without candidates via overlay before-string/after-string properties. Without
this change VoiceOver cannot read overlay-based completion UIs. this change VoiceOver cannot read overlay-based completion UIs.
* src/nsterm.m (ns_ax_face_is_selected): New static function; matches * src/nsterm.m (ns_ax_selected_overlay_text): New function; scan
'current', 'selected', 'selection' in face symbol names. overlay strings in the window for a line with a selected face; return
(ns_ax_selected_overlay_text): New function; scan overlay strings in its text.
the window for a line with a selected face; return its text. (accessibilityStringForRange:, accessibilityAttributedStringForRange:)
(accessibilityRangeForLine:): New NSAccessibility protocol methods.
Moved here from planned patch 0008 to keep the AX protocol interface
complete before notification logic uses it.
(ensureTextCache): Switch cache-validity counter from BUF_CHARS_MODIFF (ensureTextCache): Switch cache-validity counter from BUF_CHARS_MODIFF
to BUF_MODIFF. Fold/unfold commands (org-mode, outline-mode, to BUF_MODIFF. Fold/unfold commands (org-mode, outline-mode,
hideshow-mode) change the 'invisible text property via hideshow-mode) change the 'invisible text property via
@@ -27,11 +30,11 @@ granularity_unknown when the cache is absent), so font-lock passes
cannot trigger O(buffer-size) rebuilds via the notification path. cannot trigger O(buffer-size) rebuilds via the notification path.
--- ---
src/nsterm.h | 1 + src/nsterm.h | 1 +
src/nsterm.m | 358 ++++++++++++++++++++++++++++++++++++++++++++------- src/nsterm.m | 384 ++++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 316 insertions(+), 43 deletions(-) 2 files changed, 306 insertions(+), 79 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index f245675513..a210ceba14 100644 index 4bf79a9adb..72ca210bb0 100644
--- a/src/nsterm.h --- a/src/nsterm.h
+++ b/src/nsterm.h +++ b/src/nsterm.h
@@ -510,6 +510,7 @@ typedef struct ns_ax_visible_run @@ -510,6 +510,7 @@ typedef struct ns_ax_visible_run
@@ -43,7 +46,7 @@ index f245675513..a210ceba14 100644
@property (nonatomic, assign) BOOL cachedMarkActive; @property (nonatomic, assign) BOOL cachedMarkActive;
@property (nonatomic, copy) NSString *cachedCompletionAnnouncement; @property (nonatomic, copy) NSString *cachedCompletionAnnouncement;
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index a0419bb5df..b9d3a0eb53 100644 index e4e43dd7a3..c9fe93a57b 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7263,11 +7263,154 @@ Accessibility virtual elements (macOS / Cocoa only) @@ -7263,11 +7263,154 @@ Accessibility virtual elements (macOS / Cocoa only)
@@ -202,25 +205,57 @@ index a0419bb5df..b9d3a0eb53 100644
static NSString * static NSString *
ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start, ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
ns_ax_visible_run **out_runs, NSUInteger *out_nruns) ns_ax_visible_run **out_runs, NSUInteger *out_nruns)
@@ -7340,7 +7483,7 @@ Accessibility virtual elements (macOS / Cocoa only) @@ -7343,7 +7486,7 @@ Accessibility virtual elements (macOS / Cocoa only)
/* Extract this visible run's text. Use /* Extract this visible run's text. Use
Fbuffer_substring_no_properties which correctly handles the Fbuffer_substring_no_properties which correctly handles the
- buffer gap raw BUF_BYTE_ADDRESS reads across the gap would - buffer gap --- raw BUF_BYTE_ADDRESS reads across the gap would
+ buffer gap --- raw BUF_BYTE_ADDRESS reads across the gap would + buffer gap --- raw BUF_BYTE_ADDRESS reads across the gap would
include garbage bytes when the run spans the gap position. */ include garbage bytes when the run spans the gap position. */
Lisp_Object lstr = Fbuffer_substring_no_properties ( Lisp_Object lstr = Fbuffer_substring_no_properties (
make_fixnum (pos), make_fixnum (run_end)); make_fixnum (pos), make_fixnum (run_end));
@@ -7421,7 +7564,7 @@ Mode lines using icon fonts (e.g. nerd-font icons) @@ -7424,7 +7567,7 @@ Mode lines using icon fonts (e.g. nerd-font icons)
return NSZeroRect; return NSZeroRect;
/* charpos_start and charpos_len are already in buffer charpos /* charpos_start and charpos_len are already in buffer charpos
- space the caller maps AX string indices through - space --- the caller maps AX string indices through
+ space --- the caller maps AX string indices through + space --- the caller maps AX string indices through
charposForAccessibilityIndex which handles invisible text. */ charposForAccessibilityIndex which handles invisible text. */
ptrdiff_t cp_start = charpos_start; ptrdiff_t cp_start = charpos_start;
ptrdiff_t cp_end = cp_start + charpos_len; ptrdiff_t cp_end = cp_start + charpos_len;
@@ -7896,6 +8039,7 @@ @implementation EmacsAccessibilityBuffer @@ -7606,31 +7749,6 @@ already on the main queue (e.g., inside postAccessibilityUpdates
freeing the main queue for VoiceOver's dispatch_sync calls. */
-/* Return true if FACE (a symbol or list of symbols) looks like a
- "selected item" face. Substring match is intentionally broad ---
- it catches vertico-current, icomplete-selected-match,
- ivy-current-match, company-tooltip-selection, and similar.
- False positives are harmless: this runs only on overlay/child-frame
- strings during completion, never in a hot redisplay path. */
-static bool
-ns_ax_face_is_selected (Lisp_Object face)
-{
- if (SYMBOLP (face) && !NILP (face))
- {
- const char *name = SSDATA (SYMBOL_NAME (face));
- if (strstr (name, "current") || strstr (name, "selected")
- || strstr (name, "selection"))
- return true;
- }
- if (CONSP (face))
- {
- for (Lisp_Object tail = face; CONSP (tail); tail = XCDR (tail))
- if (ns_ax_face_is_selected (XCAR (tail)))
- return true;
- }
- return false;
-}
-
static inline void
ns_ax_post_notification (id element,
NSAccessibilityNotificationName name)
{
@@ -7924,6 +8043,7 @@ @implementation EmacsAccessibilityBuffer
@synthesize cachedOverlayModiff; @synthesize cachedOverlayModiff;
@synthesize cachedTextStart; @synthesize cachedTextStart;
@synthesize cachedModiff; @synthesize cachedModiff;
@@ -228,16 +263,16 @@ index a0419bb5df..b9d3a0eb53 100644
@synthesize cachedPoint; @synthesize cachedPoint;
@synthesize cachedMarkActive; @synthesize cachedMarkActive;
@synthesize cachedCompletionAnnouncement; @synthesize cachedCompletionAnnouncement;
@@ -7993,7 +8137,7 @@ - (void)ensureTextCache @@ -8021,7 +8141,7 @@ - (void)ensureTextCache
NSTRACE ("EmacsAccessibilityBuffer ensureTextCache"); NSTRACE ("EmacsAccessibilityBuffer ensureTextCache");
/* This method is only called from the main thread (AX getters /* This method is only called from the main thread (AX getters
dispatch_sync to main first). Reads of cachedText/cachedTextModiff dispatch_sync to main first). Reads of cachedText/cachedTextModiff
- below are therefore safe without @synchronized only the - below are therefore safe without @synchronized --- only the
+ below are therefore safe without @synchronized --- only the + below are therefore safe without @synchronized --- only the
write section at the end needs synchronization to protect write section at the end needs synchronization to protect
against concurrent reads from AX server thread. */ against concurrent reads from AX server thread. */
eassert ([NSThread isMainThread]); eassert ([NSThread isMainThread]);
@@ -8005,25 +8149,38 @@ - (void)ensureTextCache @@ -8033,25 +8153,38 @@ - (void)ensureTextCache
if (!b) if (!b)
return; return;
@@ -293,7 +328,7 @@ index a0419bb5df..b9d3a0eb53 100644
&& cachedTextStart == BUF_BEGV (b) && cachedTextStart == BUF_BEGV (b)
&& pt >= cachedTextStart && pt >= cachedTextStart
&& (textLen == 0 && (textLen == 0
@@ -8039,7 +8196,7 @@ included in the cached AX text (it is handled separately via @@ -8067,7 +8200,7 @@ included in the cached AX text (it is handled separately via
{ {
[cachedText release]; [cachedText release];
cachedText = [text retain]; cachedText = [text retain];
@@ -302,7 +337,7 @@ index a0419bb5df..b9d3a0eb53 100644
cachedTextStart = start; cachedTextStart = start;
if (visibleRuns) if (visibleRuns)
@@ -8051,9 +8208,9 @@ included in the cached AX text (it is handled separately via @@ -8079,9 +8212,9 @@ included in the cached AX text (it is handled separately via
Walk the cached text once, recording the start offset of each Walk the cached text once, recording the start offset of each
line. Uses NSString lineRangeForRange: --- O(N) in the total line. Uses NSString lineRangeForRange: --- O(N) in the total
text --- but this loop runs only on cache rebuild, which is text --- but this loop runs only on cache rebuild, which is
@@ -315,125 +350,142 @@ index a0419bb5df..b9d3a0eb53 100644
enters this code. */ enters this code. */
if (lineStartOffsets) if (lineStartOffsets)
xfree (lineStartOffsets); xfree (lineStartOffsets);
@@ -8108,7 +8265,7 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos @@ -8136,7 +8269,7 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos
/* Binary search: runs are sorted by charpos (ascending). Find the /* Binary search: runs are sorted by charpos (ascending). Find the
run whose [charpos, charpos+length) range contains the target, run whose [charpos, charpos+length) range contains the target,
or the nearest run after an invisible gap. O(log n) instead of or the nearest run after an invisible gap. O(log n) instead of
- O(n) matters for org-mode with many folded sections. */ - O(n) --- matters for org-mode with many folded sections. */
+ O(n) --- matters for org-mode with many folded sections. */ + O(n) --- matters for org-mode with many folded sections. */
NSUInteger lo = 0, hi = visibleRunCount; NSUInteger lo = 0, hi = visibleRunCount;
while (lo < hi) while (lo < hi)
{ {
@@ -8157,10 +8314,10 @@ by run length (visible window), not total buffer size. */ @@ -8185,10 +8318,10 @@ by run length (visible window), not total buffer size. */
/* Convert accessibility string index to buffer charpos. /* Convert accessibility string index to buffer charpos.
Safe to call from any thread: uses only cachedText (NSString) and Safe to call from any thread: uses only cachedText (NSString) and
- visibleRuns no Lisp calls. */ - visibleRuns --- no Lisp calls. */
+ visibleRuns --- no Lisp calls. */ + visibleRuns --- no Lisp calls. */
- (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx - (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx
{ {
- /* May be called from AX server thread synchronize. */ - /* May be called from AX server thread --- synchronize. */
+ /* May be called from AX server thread --- synchronize. */ + /* May be called from AX server thread --- synchronize. */
@synchronized (self) @synchronized (self)
{ {
if (visibleRunCount == 0) if (visibleRunCount == 0)
@@ -8202,7 +8359,7 @@ the slow path (composed character sequence walk), which is @@ -8230,7 +8363,7 @@ the slow path (composed character sequence walk), which is
return cp; return cp;
} }
} }
- /* Past end return last charpos. */ - /* Past end --- return last charpos. */
+ /* Past end --- return last charpos. */ + /* Past end --- return last charpos. */
if (lo > 0) if (lo > 0)
{ {
ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1];
@@ -8224,7 +8381,7 @@ the slow path (composed character sequence walk), which is @@ -8252,7 +8385,7 @@ the slow path (composed character sequence walk), which is
deadlocking the AX server thread. This is prevented by: deadlocking the AX server thread. This is prevented by:
1. validWindow checks WINDOW_LIVE_P and BUFFERP before every 1. validWindow checks WINDOW_LIVE_P and BUFFERP before every
- Lisp access the window and buffer are verified live. - Lisp access --- the window and buffer are verified live.
+ Lisp access --- the window and buffer are verified live. + Lisp access --- the window and buffer are verified live.
2. All dispatch_sync blocks run on the main thread where no 2. All dispatch_sync blocks run on the main thread where no
concurrent Lisp code can modify state between checks. concurrent Lisp code can modify state between checks.
3. block_input prevents timer events and process output from 3. block_input prevents timer events and process output from
@@ -8570,6 +8727,50 @@ - (NSInteger)accessibilityInsertionPointLineNumber @@ -8597,26 +8730,26 @@ - (NSInteger)accessibilityInsertionPointLineNumber
return [self lineForAXIndex:point_idx]; return [self lineForAXIndex:point_idx];
} }
-- (NSRange)accessibilityRangeForLine:(NSInteger)line
+- (NSString *)accessibilityStringForRange:(NSRange)range +- (NSString *)accessibilityStringForRange:(NSRange)range
+{ {
+ if (![NSThread isMainThread]) if (![NSThread isMainThread])
+ { {
- __block NSRange result;
+ __block NSString *result; + __block NSString *result;
+ dispatch_sync (dispatch_get_main_queue (), ^{ dispatch_sync (dispatch_get_main_queue (), ^{
- result = [self accessibilityRangeForLine:line];
+ result = [self accessibilityStringForRange:range]; + result = [self accessibilityStringForRange:range];
+ }); });
+ return result; return result;
+ } }
+ [self ensureTextCache]; [self ensureTextCache];
- if (!cachedText || line < 0)
- return NSMakeRange (NSNotFound, 0);
-
- NSUInteger len = [cachedText length];
- if (len == 0)
- return (line == 0) ? NSMakeRange (0, 0)
- : NSMakeRange (NSNotFound, 0);
+ if (!cachedText || range.location + range.length > [cachedText length]) + if (!cachedText || range.location + range.length > [cachedText length])
+ return @""; + return @"";
+ return [cachedText substringWithRange:range]; + return [cachedText substringWithRange:range];
+} +}
+
- return [self rangeForLine:(NSUInteger)line textLength:len];
+- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)range +- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)range
+{ +{
+ NSString *str = [self accessibilityStringForRange:range]; + NSString *str = [self accessibilityStringForRange:range];
+ return [[[NSAttributedString alloc] initWithString:str] autorelease]; + return [[[NSAttributedString alloc] initWithString:str] autorelease];
}
- (NSInteger)accessibilityLineForIndex:(NSInteger)index
@@ -8638,6 +8771,29 @@ - (NSInteger)accessibilityLineForIndex:(NSInteger)index
idx = [cachedText length];
return [self lineForAXIndex:idx];
+
+} +}
+ +
+- (NSInteger)accessibilityLineForIndex:(NSInteger)index +- (NSRange)accessibilityRangeForLine:(NSInteger)line
+{ +{
+ if (![NSThread isMainThread]) + if (![NSThread isMainThread])
+ { + {
+ __block NSInteger result; + __block NSRange result;
+ dispatch_sync (dispatch_get_main_queue (), ^{ + dispatch_sync (dispatch_get_main_queue (), ^{
+ result = [self accessibilityLineForIndex:index]; + result = [self accessibilityRangeForLine:line];
+ }); + });
+ return result; + return result;
+ } + }
+ [self ensureTextCache]; + [self ensureTextCache];
+ if (!cachedText || index < 0) + if (!cachedText || line < 0)
+ return 0; + return NSMakeRange (NSNotFound, 0);
+ +
+ NSUInteger idx = (NSUInteger) index; + NSUInteger len = [cachedText length];
+ if (idx > [cachedText length]) + if (len == 0)
+ idx = [cachedText length]; + return (line == 0) ? NSMakeRange (0, 0)
+ : NSMakeRange (NSNotFound, 0);
+ +
+ return [self lineForAXIndex:idx]; + return [self rangeForLine:(NSUInteger)line textLength:len];
+ }
+}
+ - (NSRange)accessibilityRangeForIndex:(NSInteger)index
- (NSRange)accessibilityRangeForLine:(NSInteger)line @@ -8840,7 +8996,7 @@ - (NSRect)accessibilityFrame
{
if (![NSThread isMainThread])
@@ -8792,7 +8993,7 @@ - (NSRect)accessibilityFrame
/* =================================================================== /* ===================================================================
- EmacsAccessibilityBuffer (Notifications) AX event dispatch - EmacsAccessibilityBuffer (Notifications) --- AX event dispatch
+ EmacsAccessibilityBuffer (Notifications) --- AX event dispatch + EmacsAccessibilityBuffer (Notifications) --- AX event dispatch
These methods notify VoiceOver of text and selection changes. These methods notify VoiceOver of text and selection changes.
Called from the redisplay cycle (postAccessibilityUpdates). Called from the redisplay cycle (postAccessibilityUpdates).
@@ -8807,7 +9008,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point @@ -8855,7 +9011,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
if (point > self.cachedPoint if (point > self.cachedPoint
&& point - self.cachedPoint == 1) && point - self.cachedPoint == 1)
{ {
- /* Single char inserted refresh cache and grab it. */ - /* Single char inserted --- refresh cache and grab it. */
+ /* Single char inserted --- refresh cache and grab it. */ + /* Single char inserted --- refresh cache and grab it. */
[self invalidateTextCache]; [self invalidateTextCache];
[self ensureTextCache]; [self ensureTextCache];
if (cachedText) if (cachedText)
@@ -8826,7 +9027,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point @@ -8874,7 +9030,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
/* Update cachedPoint here so the selection-move branch does NOT /* Update cachedPoint here so the selection-move branch does NOT
fire for point changes caused by edits. WebKit and Chromium fire for point changes caused by edits. WebKit and Chromium
never send both ValueChanged and SelectedTextChanged for the never send both ValueChanged and SelectedTextChanged for the
- same user action they are mutually exclusive. */ - same user action --- they are mutually exclusive. */
+ same user action --- they are mutually exclusive. */ + same user action --- they are mutually exclusive. */
self.cachedPoint = point; self.cachedPoint = point;
NSDictionary *change = @{ NSDictionary *change = @{
@@ -9220,16 +9421,80 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9268,16 +9424,80 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
BOOL markActive = !NILP (BVAR (b, mark_active)); BOOL markActive = !NILP (BVAR (b, mark_active));
/* --- Text changed (edit) --- */ /* --- Text changed (edit) --- */
@@ -510,7 +562,7 @@ index a0419bb5df..b9d3a0eb53 100644
} }
/* --- Cursor moved or selection changed --- /* --- Cursor moved or selection changed ---
- Use 'else if' edits and selection moves are mutually exclusive - Use 'else if' --- edits and selection moves are mutually exclusive
- per the WebKit/Chromium pattern. */ - per the WebKit/Chromium pattern. */
- else if (point != self.cachedPoint || markActive != self.cachedMarkActive) - else if (point != self.cachedPoint || markActive != self.cachedMarkActive)
+ Independent check from the overlay branch above. */ + Independent check from the overlay branch above. */
@@ -518,7 +570,7 @@ index a0419bb5df..b9d3a0eb53 100644
{ {
ptrdiff_t oldPoint = self.cachedPoint; ptrdiff_t oldPoint = self.cachedPoint;
BOOL oldMarkActive = self.cachedMarkActive; BOOL oldMarkActive = self.cachedMarkActive;
@@ -9247,8 +9512,15 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9295,8 +9515,14 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
bool isCtrlNP = ns_ax_event_is_line_nav_key (&ctrlNP); bool isCtrlNP = ns_ax_event_is_line_nav_key (&ctrlNP);
/* --- Granularity detection --- */ /* --- Granularity detection --- */
@@ -531,48 +583,47 @@ index a0419bb5df..b9d3a0eb53 100644
+ to VoiceOver via the AX getter path (accessibilityValue etc.). */ + to VoiceOver via the AX getter path (accessibilityValue etc.). */
NSInteger granularity = ns_ax_text_selection_granularity_unknown; NSInteger granularity = ns_ax_text_selection_granularity_unknown;
- [self ensureTextCache]; - [self ensureTextCache];
+ BOOL singleLineMove = NO;
if (cachedText && oldPoint > 0) if (cachedText && oldPoint > 0)
{ {
NSUInteger tlen = [cachedText length]; NSUInteger tlen = [cachedText length];
@@ -12408,7 +12680,7 @@ - (int) fullscreenState @@ -12457,7 +12683,7 @@ - (int) fullscreenState
if (WINDOW_LEAF_P (w)) if (WINDOW_LEAF_P (w))
{ {
- /* Buffer element reuse existing if available. */ - /* Buffer element --- reuse existing if available. */
+ /* Buffer element --- reuse existing if available. */ + /* Buffer element --- reuse existing if available. */
EmacsAccessibilityBuffer *elem EmacsAccessibilityBuffer *elem
= [existing objectForKey:[NSValue valueWithPointer:w]]; = [existing objectForKey:[NSValue valueWithPointer:w]];
if (!elem) if (!elem)
@@ -12442,7 +12714,7 @@ - (int) fullscreenState @@ -12491,7 +12717,7 @@ - (int) fullscreenState
} }
else else
{ {
- /* Internal (combination) window recurse into children. */ - /* Internal (combination) window --- recurse into children. */
+ /* Internal (combination) window --- recurse into children. */ + /* Internal (combination) window --- recurse into children. */
Lisp_Object child = w->contents; Lisp_Object child = w->contents;
while (!NILP (child)) while (!NILP (child))
{ {
@@ -12554,7 +12826,7 @@ - (void)postAccessibilityUpdates @@ -12603,7 +12829,7 @@ - (void)postAccessibilityUpdates
accessibilityUpdating = YES; accessibilityUpdating = YES;
/* Detect window tree change (split, delete, new buffer). Compare /* Detect window tree change (split, delete, new buffer). Compare
- FRAME_ROOT_WINDOW if it changed, the tree structure changed. */ - FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */
+ FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */ + FRAME_ROOT_WINDOW --- if it changed, the tree structure changed. */
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe); Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
if (!EQ (curRoot, lastRootWindow)) if (!EQ (curRoot, lastRootWindow))
{ {
@@ -12563,12 +12835,12 @@ - (void)postAccessibilityUpdates @@ -12612,12 +12838,12 @@ - (void)postAccessibilityUpdates
} }
/* If tree is stale, rebuild FIRST so we don't iterate freed /* If tree is stale, rebuild FIRST so we don't iterate freed
- window pointers. Skip notifications for this cycle the - window pointers. Skip notifications for this cycle --- the
+ window pointers. Skip notifications for this cycle --- the + window pointers. Skip notifications for this cycle --- the
freshly-built elements have no previous state to diff against. */ freshly-built elements have no previous state to diff against. */
if (!accessibilityTreeValid) if (!accessibilityTreeValid)
{ {
[self rebuildAccessibilityTree]; [self rebuildAccessibilityTree];
- /* Invalidate span cache window layout changed. */ - /* Invalidate span cache --- window layout changed. */
+ /* Invalidate span cache --- window layout changed. */ + /* Invalidate span cache --- window layout changed. */
for (EmacsAccessibilityElement *elem in accessibilityElements) for (EmacsAccessibilityElement *elem in accessibilityElements)
if ([elem isKindOfClass: [EmacsAccessibilityBuffer class]]) if ([elem isKindOfClass: [EmacsAccessibilityBuffer class]])

View File

@@ -1,4 +1,4 @@
From b54ed57b93cb47250695106021d6e96030ffdd59 Mon Sep 17 00:00:00 2001 From 5bef7fa553d0dfd9ab933d341a8115d42e026b42 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Mon, 2 Mar 2026 18:49:13 +0100 Date: Mon, 2 Mar 2026 18:49:13 +0100
Subject: [PATCH 9/9] ns: announce child frame completion candidates for Subject: [PATCH 9/9] ns: announce child frame completion candidates for
@@ -37,8 +37,8 @@ Remove Zoom section (covered by patch 0000). Fix dangling paragraph.
doc/emacs/macos.texi | 13 +- doc/emacs/macos.texi | 13 +-
etc/NEWS | 25 +- etc/NEWS | 25 +-
src/nsterm.h | 21 ++ src/nsterm.h | 21 ++
src/nsterm.m | 560 +++++++++++++++++++++++++++++++++++++------ src/nsterm.m | 577 +++++++++++++++++++++++++++++++++++++------
4 files changed, 528 insertions(+), 91 deletions(-) 4 files changed, 541 insertions(+), 95 deletions(-)
diff --git a/doc/emacs/macos.texi b/doc/emacs/macos.texi diff --git a/doc/emacs/macos.texi b/doc/emacs/macos.texi
index 72ac3a9aa9..cf5ed0ff28 100644 index 72ac3a9aa9..cf5ed0ff28 100644
@@ -111,7 +111,7 @@ index 7f917f93b2..bbec21b635 100644
interface and eliminate the associated overhead. interface and eliminate the associated overhead.
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index a210ceba14..2edd7cd6e0 100644 index 72ca210bb0..1c79c8aced 100644
--- a/src/nsterm.h --- a/src/nsterm.h
+++ b/src/nsterm.h +++ b/src/nsterm.h
@@ -504,9 +504,20 @@ typedef struct ns_ax_visible_run @@ -504,9 +504,20 @@ typedef struct ns_ax_visible_run
@@ -135,12 +135,12 @@ index a210ceba14..2edd7cd6e0 100644
@property (nonatomic, assign) ptrdiff_t cachedOverlayModiff; @property (nonatomic, assign) ptrdiff_t cachedOverlayModiff;
@property (nonatomic, assign) ptrdiff_t cachedTextStart; @property (nonatomic, assign) ptrdiff_t cachedTextStart;
@property (nonatomic, assign) ptrdiff_t cachedModiff; @property (nonatomic, assign) ptrdiff_t cachedModiff;
@@ -596,6 +607,14 @@ typedef NS_ENUM(NSInteger, EmacsAXSpanType) @@ -601,6 +612,14 @@ typedef NS_ENUM(NSInteger, EmacsAXSpanType)
Lisp_Object lastRootWindow; Lisp_Object lastRootWindow;
BOOL accessibilityTreeValid; BOOL accessibilityTreeValid;
BOOL accessibilityUpdating; BOOL accessibilityUpdating;
+ BOOL childFrameCompletionActive; + BOOL childFrameCompletionActive;
+ char *childFrameLastCandidate; + NSString *childFrameLastCandidate;
+ Lisp_Object childFrameLastBuffer; + Lisp_Object childFrameLastBuffer;
+ EMACS_INT childFrameLastModiff; + EMACS_INT childFrameLastModiff;
+ /* Last BUF_CHARS_MODIFF seen for echo_area_buffer[0]. Used by + /* Last BUF_CHARS_MODIFF seen for echo_area_buffer[0]. Used by
@@ -150,7 +150,7 @@ index a210ceba14..2edd7cd6e0 100644
#endif #endif
BOOL font_panel_active; BOOL font_panel_active;
NSFont *font_panel_result; NSFont *font_panel_result;
@@ -665,6 +684,8 @@ typedef NS_ENUM(NSInteger, EmacsAXSpanType) @@ -670,6 +689,8 @@ typedef NS_ENUM(NSInteger, EmacsAXSpanType)
- (void)rebuildAccessibilityTree; - (void)rebuildAccessibilityTree;
- (void)invalidateAccessibilityTree; - (void)invalidateAccessibilityTree;
- (void)postAccessibilityUpdates; - (void)postAccessibilityUpdates;
@@ -160,7 +160,7 @@ index a210ceba14..2edd7cd6e0 100644
@end @end
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index b9d3a0eb53..5e48710930 100644 index c9fe93a57b..f7574efb39 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1275,6 +1275,12 @@ If a completion candidate is selected (overlay or child frame), @@ -1275,6 +1275,12 @@ If a completion candidate is selected (overlay or child frame),
@@ -176,7 +176,23 @@ index b9d3a0eb53..5e48710930 100644
if (!ns_zoom_enabled_p ()) if (!ns_zoom_enabled_p ())
return; return;
if (!WINDOWP (f->selected_window)) if (!WINDOWP (f->selected_window))
@@ -7407,6 +7413,117 @@ visual line index for Zoom (skip whitespace-only lines @@ -1417,9 +1423,14 @@ so the visual offset is (ov_line + 1) * line_h from
/* Track completion candidates for Zoom (overlay and child frame).
Runs after cursor tracking so the selected candidate overrides
- the default cursor position. */
+ the default cursor position. Guard with the same version check
+ as ns_zoom_track_completion's callee (UAZoomChangeFocus requires
+ macOS 10.10+). */
+#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
+ && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (view)
ns_zoom_track_completion (f, view);
+#endif /* MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 */
#endif /* NS_IMPL_COCOA */
/* Post accessibility notifications after each redisplay cycle. */
@@ -7407,6 +7418,119 @@ visual line index for Zoom (skip whitespace-only lines
return nil; return nil;
} }
@@ -226,14 +242,16 @@ index b9d3a0eb53..5e48710930 100644
+ /* 128 lines is a safe upper bound for a completion child frame. + /* 128 lines is a safe upper bound for a completion child frame.
+ The caller rejects buffers larger than 10,000 characters + The caller rejects buffers larger than 10,000 characters
+ (BUF_ZV(b) - BUF_BEGV(b) > 10000 guard in announceChildFrameCompletion), + (BUF_ZV(b) - BUF_BEGV(b) > 10000 guard in announceChildFrameCompletion),
+ so the worst case is ~10 KB / 1 byte per line < 128. If a future + so at most ~10,000 / 1-byte-per-line = 10,000 lines could appear,
+ caller removes that guard, lines beyond 128 are silently skipped; */ + but completion popups are typically < 512 lines. Use 512 to match
+ ptrdiff_t line_starts[128]; + the bound in ns_ax_selected_overlay_text; lines beyond 512 are
+ ptrdiff_t line_ends[128]; + silently skipped. */
+ ptrdiff_t line_starts[512];
+ ptrdiff_t line_ends[512];
+ int nlines = 0; + int nlines = 0;
+ ptrdiff_t char_pos = 0, byte_pos = 0, lstart = 0; + ptrdiff_t char_pos = 0, byte_pos = 0, lstart = 0;
+ +
+ while (byte_pos < byte_len && nlines < 128) + while (byte_pos < byte_len && nlines < 512)
+ { + {
+ if (data[byte_pos] == '\n') + if (data[byte_pos] == '\n')
+ { + {
@@ -251,7 +269,7 @@ index b9d3a0eb53..5e48710930 100644
+ byte_pos++; + byte_pos++;
+ char_pos++; + char_pos++;
+ } + }
+ if (char_pos > lstart && nlines < 128) + if (char_pos > lstart && nlines < 512)
+ { + {
+ line_starts[nlines] = lstart; + line_starts[nlines] = lstart;
+ line_ends[nlines] = char_pos; + line_ends[nlines] = char_pos;
@@ -294,22 +312,24 @@ index b9d3a0eb53..5e48710930 100644
/* Build accessibility text for window W, skipping invisible text. /* Build accessibility text for window W, skipping invisible text.
Populates *OUT_START with the buffer start charpos. Populates *OUT_START with the buffer start charpos.
Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
@@ -7440,9 +7557,13 @@ visual line index for Zoom (skip whitespace-only lines @@ -7440,11 +7564,12 @@ visual line index for Zoom (skip whitespace-only lines
return @""; return @"";
specpdl_ref count = SPECPDL_INDEX (); specpdl_ref count = SPECPDL_INDEX ();
- record_unwind_current_buffer ();
- /* block_input must come before record_unwind_protect_void (unblock_input):
- if specpdl_push were to fail after registration, the unwind handler
- would call unblock_input without a matching block_input. */
+ /* block_input must precede record_unwind_protect_void (unblock_input): + /* block_input must precede record_unwind_protect_void (unblock_input):
+ if anything between SPECPDL_INDEX and block_input were to throw, + if anything between SPECPDL_INDEX and block_input were to throw,
+ the unwind handler would call unblock_input without a matching + the unwind handler would call unblock_input without a matching
+ block_input, corrupting the input-blocking reference count. */ + block_input, corrupting the input-blocking reference count. */
+ block_input (); block_input ();
record_unwind_current_buffer (); + record_unwind_current_buffer ();
record_unwind_protect_void (unblock_input); record_unwind_protect_void (unblock_input);
- block_input ();
if (b != current_buffer) if (b != current_buffer)
set_buffer_internal_1 (b); set_buffer_internal_1 (b);
@@ -8613,6 +8738,11 @@ - (void)setAccessibilitySelectedTextRange:(NSRange)range
@@ -8609,6 +8730,11 @@ - (void)setAccessibilitySelectedTextRange:(NSRange)range
[self ensureTextCache]; [self ensureTextCache];
@@ -320,8 +340,8 @@ index b9d3a0eb53..5e48710930 100644
+ +
specpdl_ref count = SPECPDL_INDEX (); specpdl_ref count = SPECPDL_INDEX ();
record_unwind_current_buffer (); record_unwind_current_buffer ();
/* Ensure block_input is always matched by unblock_input even if /* block_input must come before record_unwind_protect_void (unblock_input). */
@@ -9057,20 +9183,38 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point @@ -9060,20 +9190,38 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point
&& granularity && granularity
== ns_ax_text_selection_granularity_character); == ns_ax_text_selection_granularity_character);
@@ -370,7 +390,7 @@ index b9d3a0eb53..5e48710930 100644
ns_ax_post_notification_with_info ( ns_ax_post_notification_with_info (
self, self,
NSAccessibilitySelectedTextChangedNotification, NSAccessibilitySelectedTextChangedNotification,
@@ -9170,12 +9314,17 @@ user expectation ("w" jumps to next word and reads it). */ @@ -9173,12 +9321,17 @@ user expectation ("w" jumps to next word and reads it). */
} }
} }
@@ -393,7 +413,7 @@ index b9d3a0eb53..5e48710930 100644
if (cachedText if (cachedText
&& granularity == ns_ax_text_selection_granularity_line) && granularity == ns_ax_text_selection_granularity_line)
{ {
@@ -9240,6 +9389,11 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b @@ -9243,6 +9396,11 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
block_input (); block_input ();
specpdl_ref count2 = SPECPDL_INDEX (); specpdl_ref count2 = SPECPDL_INDEX ();
@@ -405,7 +425,7 @@ index b9d3a0eb53..5e48710930 100644
record_unwind_protect_void (unblock_input); record_unwind_protect_void (unblock_input);
record_unwind_current_buffer (); record_unwind_current_buffer ();
if (b != current_buffer) if (b != current_buffer)
@@ -9416,12 +9570,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9419,12 +9577,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
if (!b) if (!b)
return; return;
@@ -413,7 +433,7 @@ index b9d3a0eb53..5e48710930 100644
+ postEchoAreaAnnouncementIfNeeded (called from postAccessibilityUpdates + postEchoAreaAnnouncementIfNeeded (called from postAccessibilityUpdates
+ before this per-element loop) so that they are never lost to a + before this per-element loop) so that they are never lost to a
+ concurrent tree rebuild. For the inactive minibuffer (minibuf_level + concurrent tree rebuild. For the inactive minibuffer (minibuf_level
+ == 0), skip normal cursor and completion processing there is no + == 0), skip normal cursor and completion processing --- there is no
+ meaningful cursor to track. */ + meaningful cursor to track. */
+ if (MINI_WINDOW_P (w) && minibuf_level == 0) + if (MINI_WINDOW_P (w) && minibuf_level == 0)
+ return; + return;
@@ -435,7 +455,7 @@ index b9d3a0eb53..5e48710930 100644
if (modiff != self.cachedModiff) if (modiff != self.cachedModiff)
{ {
self.cachedModiff = modiff; self.cachedModiff = modiff;
@@ -9435,6 +9606,7 @@ Text property changes (e.g. face updates from @@ -9438,6 +9613,7 @@ Text property changes (e.g. face updates from
{ {
self.cachedCharsModiff = chars_modiff; self.cachedCharsModiff = chars_modiff;
[self postTextChangedNotification:point]; [self postTextChangedNotification:point];
@@ -443,7 +463,7 @@ index b9d3a0eb53..5e48710930 100644
} }
} }
@@ -9457,37 +9629,44 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property @@ -9460,41 +9636,49 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
displayed in the minibuffer. In normal editing buffers, displayed in the minibuffer. In normal editing buffers,
font-lock and other modes change BUF_OVERLAY_MODIFF on font-lock and other modes change BUF_OVERLAY_MODIFF on
every redisplay, triggering O(overlays) work per keystroke. every redisplay, triggering O(overlays) work per keystroke.
@@ -457,24 +477,26 @@ index b9d3a0eb53..5e48710930 100644
+ echo produced by postTextChangedNotification, making typed + echo produced by postTextChangedNotification, making typed
+ characters inaudible. VoiceOver should read the overlay + characters inaudible. VoiceOver should read the overlay
+ candidate only when the user navigates (C-n/C-p), not types. */ + candidate only when the user navigates (C-n/C-p), not types. */
+ if (!MINI_WINDOW_P (w) || didTextChange) + if (MINI_WINDOW_P (w) && !didTextChange)
+ goto skip_overlay_scan; + {
+ +
+ int selected_line = -1; + int selected_line = -1;
+ NSString *candidate + NSString *candidate
+ = ns_ax_selected_overlay_text (b, BUF_BEGV (b), BUF_ZV (b), + = ns_ax_selected_overlay_text (b, BUF_BEGV (b), BUF_ZV (b),
+ &selected_line); + &selected_line);
+ if (candidate) + if (candidate)
{ - {
+ {
- int selected_line = -1; - int selected_line = -1;
- NSString *candidate - NSString *candidate
- = ns_ax_selected_overlay_text (b, BUF_BEGV (b), BUF_ZV (b), - = ns_ax_selected_overlay_text (b, BUF_BEGV (b), BUF_ZV (b),
- &selected_line); - &selected_line);
- if (candidate) - if (candidate)
+ /* Deduplicate: only announce when the candidate changed. */ + /* Deduplicate: only announce when the candidate changed. */
+ if (![candidate isEqualToString: + if (![candidate isEqualToString:
+ self.cachedCompletionAnnouncement]) + self.cachedCompletionAnnouncement])
{ - {
+ {
- /* Deduplicate: only announce when the candidate changed. */ - /* Deduplicate: only announce when the candidate changed. */
- if (![candidate isEqualToString: - if (![candidate isEqualToString:
- self.cachedCompletionAnnouncement]) - self.cachedCompletionAnnouncement])
@@ -497,27 +519,34 @@ index b9d3a0eb53..5e48710930 100644
- NSAccessibilityAnnouncementRequestedNotification, - NSAccessibilityAnnouncementRequestedNotification,
- annInfo); - annInfo);
- } - }
+ self.cachedCompletionAnnouncement = candidate; + self.cachedCompletionAnnouncement = candidate;
+ +
+ /* Announce the candidate text directly via NSApp. + /* Announce the candidate text directly via NSApp.
+ Do NOT post SelectedTextChanged --- that would cause + Do NOT post SelectedTextChanged --- that would cause
+ VoiceOver to read the AX text at the cursor position + VoiceOver to read the AX text at the cursor position
+ (the minibuffer input line), not the overlay candidate. + (the minibuffer input line), not the overlay candidate.
+ AnnouncementRequested with High priority interrupts + AnnouncementRequested with High priority interrupts
+ any current speech and announces our text. */ + any current speech and announces our text. */
+ NSDictionary *annInfo = @{ + NSDictionary *annInfo = @{
+ NSAccessibilityAnnouncementKey: candidate, + NSAccessibilityAnnouncementKey: candidate,
+ NSAccessibilityPriorityKey: + NSAccessibilityPriorityKey:
+ @(NSAccessibilityPriorityHigh) + @(NSAccessibilityPriorityHigh)
+ }; + };
+ ns_ax_post_notification_with_info ( + ns_ax_post_notification_with_info (
+ NSApp, + NSApp,
+ NSAccessibilityAnnouncementRequestedNotification, + NSAccessibilityAnnouncementRequestedNotification,
+ annInfo); + annInfo);
} - }
} + }
- }
+ }
+ }
} }
@@ -9501,7 +9680,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
/* --- Cursor moved or selection changed ---
Independent check from the overlay branch above. */
if (point != self.cachedPoint || markActive != self.cachedMarkActive)
@@ -9504,7 +9688,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
self.cachedPoint = point; self.cachedPoint = point;
self.cachedMarkActive = markActive; self.cachedMarkActive = markActive;
@@ -537,7 +566,15 @@ index b9d3a0eb53..5e48710930 100644
NSInteger direction = ns_ax_text_selection_direction_discontiguous; NSInteger direction = ns_ax_text_selection_direction_discontiguous;
if (point > oldPoint) if (point > oldPoint)
direction = ns_ax_text_selection_direction_next; direction = ns_ax_text_selection_direction_next;
@@ -9534,7 +9724,18 @@ to VoiceOver via the AX getter path (accessibilityValue etc.). */ @@ -9523,6 +9718,7 @@ granularity hint (defaulting to unknown), which causes VoiceOver
to make its own determination. Fresh text is always available
to VoiceOver via the AX getter path (accessibilityValue etc.). */
NSInteger granularity = ns_ax_text_selection_granularity_unknown;
+ BOOL singleLineMove = NO;
if (cachedText && oldPoint > 0)
{
NSUInteger tlen = [cachedText length];
@@ -9536,7 +9732,18 @@ to VoiceOver via the AX getter path (accessibilityValue etc.). */
NSRange newLine = [cachedText lineRangeForRange: NSRange newLine = [cachedText lineRangeForRange:
NSMakeRange (newIdx, 0)]; NSMakeRange (newIdx, 0)];
if (oldLine.location != newLine.location) if (oldLine.location != newLine.location)
@@ -557,7 +594,7 @@ index b9d3a0eb53..5e48710930 100644
else else
{ {
NSUInteger dist = (newIdx > oldIdx NSUInteger dist = (newIdx > oldIdx
@@ -9556,38 +9757,23 @@ to VoiceOver via the AX getter path (accessibilityValue etc.). */ @@ -9558,38 +9765,23 @@ to VoiceOver via the AX getter path (accessibilityValue etc.). */
granularity = ns_ax_text_selection_granularity_line; granularity = ns_ax_text_selection_granularity_line;
} }
@@ -609,7 +646,7 @@ index b9d3a0eb53..5e48710930 100644
{ {
NSWindow *win = [self.emacsView window]; NSWindow *win = [self.emacsView window];
if (win) if (win)
@@ -9746,6 +9932,13 @@ - (NSRect)accessibilityFrame @@ -9748,6 +9940,13 @@ - (NSRect)accessibilityFrame
if (vis_start >= vis_end) if (vis_start >= vis_end)
return @[]; return @[];
@@ -623,18 +660,17 @@ index b9d3a0eb53..5e48710930 100644
block_input (); block_input ();
specpdl_ref blk_count = SPECPDL_INDEX (); specpdl_ref blk_count = SPECPDL_INDEX ();
record_unwind_protect_void (unblock_input); record_unwind_protect_void (unblock_input);
@@ -10053,6 +10246,10 @@ - (void)dealloc @@ -10056,6 +10255,9 @@ - (void)dealloc
#endif #endif
[accessibilityElements release]; [accessibilityElements release];
+#ifdef NS_IMPL_COCOA +#ifdef NS_IMPL_COCOA
+ if (childFrameLastCandidate) + [childFrameLastCandidate release];
+ xfree (childFrameLastCandidate);
+#endif +#endif
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -11502,6 +11699,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f @@ -11505,6 +11708,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
windowClosing = NO; windowClosing = NO;
processingCompose = NO; processingCompose = NO;
@@ -644,7 +680,7 @@ index b9d3a0eb53..5e48710930 100644
scrollbarsNeedingUpdate = 0; scrollbarsNeedingUpdate = 0;
fs_state = FULLSCREEN_NONE; fs_state = FULLSCREEN_NONE;
fs_before_fs = next_maximized = -1; fs_before_fs = next_maximized = -1;
@@ -12810,6 +13010,156 @@ - (id)accessibilityFocusedUIElement @@ -12813,6 +13019,158 @@ - (id)accessibilityFocusedUIElement
The existing elements carry cached state (modiff, point) from the The existing elements carry cached state (modiff, point) from the
previous redisplay cycle. Rebuilding first would create fresh previous redisplay cycle. Rebuilding first would create fresh
elements with current values, making change detection impossible. */ elements with current values, making change detection impossible. */
@@ -664,7 +700,10 @@ index b9d3a0eb53..5e48710930 100644
+ Reads echo_area_buffer[0] directly because with_echo_area_buffer() + Reads echo_area_buffer[0] directly because with_echo_area_buffer()
+ sets current_buffer via set_buffer_internal_1() but does NOT call + sets current_buffer via set_buffer_internal_1() but does NOT call
+ Fset_window_buffer(), so the minibuffer window's contents pointer + Fset_window_buffer(), so the minibuffer window's contents pointer
+ still points to the inactive " *Minibuf-0*" buffer. */ + still points to the inactive " *Minibuf-0*" buffer.
+ echo_area_buffer[] is maintained by setup_echo_area_for_printing()
+ and clear_message() in xdisp.c; its lifetime is the process lifetime
+ and it is valid whenever BUFFERP (echo_area_buffer[0]) is true. */
+- (void)postEchoAreaAnnouncementIfNeeded +- (void)postEchoAreaAnnouncementIfNeeded
+{ +{
+ if (minibuf_level != 0) + if (minibuf_level != 0)
@@ -729,19 +768,19 @@ index b9d3a0eb53..5e48710930 100644
+ if (!BUFFER_LIVE_P (b)) + if (!BUFFER_LIVE_P (b))
+ return; + return;
+ EMACS_INT modiff = BUF_MODIFF (b); + EMACS_INT modiff = BUF_MODIFF (b);
+ /* Compare buffer identity via the buffer name symbol, which is always + /* Compare buffer identity via the buffer name symbol. Interned
+ GC-reachable through the obarray. Storing the name avoids keeping + symbols (obarray) are GC-reachable without staticpro(), avoiding
+ a direct buffer pointer in a non-GC-visible ObjC ivar: if the buffer + a direct struct buffer pointer in a non-GC-visible ObjC ivar.
+ were killed and GC swept, a stale make_lisp_ptr value could collide + Caveat: if the buffer is renamed (rename-buffer), the stored
+ with a newly-allocated buffer at the same address. */ + symbol no longer matches the new name and the equality check
+ returns nil, causing one redundant re-scan. This is harmless ---
+ completion popups (Corfu, Company) are never renamed during a
+ completion session. Using a sequence number would avoid the
+ rename edge case but would require another ivar; the name symbol
+ is a pragmatic, GC-safe approximation. */
+ if (EQ (childFrameLastBuffer, BVAR (b, name)) + if (EQ (childFrameLastBuffer, BVAR (b, name))
+ && modiff == childFrameLastModiff) + && modiff == childFrameLastModiff)
+ return; + return;
+ /* Store the buffer name symbol (an interned Lisp_Object from
+ obarray) rather than a raw pointer to struct buffer.
+ Interned symbols are reachable from obarray and will not be
+ garbage-collected, so no staticpro() registration is needed
+ for this ivar. */
+ childFrameLastBuffer = BVAR (b, name); + childFrameLastBuffer = BVAR (b, name);
+ childFrameLastModiff = modiff; + childFrameLastModiff = modiff;
+ +
@@ -768,11 +807,10 @@ index b9d3a0eb53..5e48710930 100644
+ return; + return;
+ +
+ /* Deduplicate --- avoid re-announcing the same candidate. */ + /* Deduplicate --- avoid re-announcing the same candidate. */
+ const char *cstr = [candidate UTF8String]; + if ([candidate isEqualToString:childFrameLastCandidate])
+ if (childFrameLastCandidate && strcmp (cstr, childFrameLastCandidate) == 0)
+ return; + return;
+ xfree (childFrameLastCandidate); + [childFrameLastCandidate release];
+ childFrameLastCandidate = xstrdup (cstr); + childFrameLastCandidate = [candidate copy];
+ +
+ NSDictionary *annInfo = @{ + NSDictionary *annInfo = @{
+ NSAccessibilityAnnouncementKey: candidate, + NSAccessibilityAnnouncementKey: candidate,
@@ -801,7 +839,7 @@ index b9d3a0eb53..5e48710930 100644
- (void)postAccessibilityUpdates - (void)postAccessibilityUpdates
{ {
NSTRACE ("[EmacsView postAccessibilityUpdates]"); NSTRACE ("[EmacsView postAccessibilityUpdates]");
@@ -12820,11 +13170,69 @@ - (void)postAccessibilityUpdates @@ -12823,14 +13182,71 @@ - (void)postAccessibilityUpdates
/* Re-entrance guard: VoiceOver callbacks during notification posting /* Re-entrance guard: VoiceOver callbacks during notification posting
can trigger redisplay, which calls ns_update_end, which calls us can trigger redisplay, which calls ns_update_end, which calls us