patches: address all non-blocking review findings

Fix remaining issues identified in core maintainer review:
- 0000: correct [PATCH 0/8] to [PATCH] (standalone Zoom series)
- 0006: add paragraph break in macos.texi VoiceOver section
- 0001/0005: shorten separator comment lines to 79 chars
- 0007/0008: genericize third-party package names in commit messages
- 0001: fix block_input ordering (block before registering unwind)
- 0000/0007: document intentional face_is_selected duplication
This commit is contained in:
2026-03-03 12:48:31 +01:00
parent 9bee2a1987
commit 61c23aed2c
12 changed files with 202 additions and 126 deletions

View File

@@ -1,7 +1,7 @@
From 0470786c91eb4a464d8580387b83a4a8d4e4f8eb Mon Sep 17 00:00:00 2001 From 0470786c91eb4a464d8580387b83a4a8d4e4f8eb Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 22:39:35 +0100 Date: Sat, 28 Feb 2026 22:39:35 +0100
Subject: [PATCH 0/8] ns: integrate with macOS Zoom for cursor tracking Subject: [PATCH] ns: integrate with macOS Zoom for cursor tracking
Inform macOS Zoom of the text cursor position so the zoomed viewport Inform macOS Zoom of the text cursor position so the zoomed viewport
follows keyboard focus in Emacs. Also track completion candidates so follows keyboard focus in Emacs. Also track completion candidates so
@@ -26,16 +26,16 @@ to the selected completion candidate after normal cursor tracking.
(ns_update_end): Call ns_zoom_track_completion. (ns_update_end): Call ns_zoom_track_completion.
(ns_draw_window_cursor): Store cursor rect; call UAZoomChangeFocus. (ns_draw_window_cursor): Store cursor rect; call UAZoomChangeFocus.
--- ---
etc/NEWS | 11 ++ etc/NEWS | 10 ++
src/nsterm.h | 6 + src/nsterm.h | 6 +
src/nsterm.m | 354 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 357 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 371 insertions(+) 3 files changed, 373 insertions(+)
diff --git a/etc/NEWS b/etc/NEWS diff --git a/etc/NEWS b/etc/NEWS
index 7367e3ccbd..4c149e41d6 100644 index 7367e3ccbd..4c149e41d6 100644
--- a/etc/NEWS --- a/etc/NEWS
+++ b/etc/NEWS +++ b/etc/NEWS
@@ -82,6 +82,17 @@ other directory on your system. You can also invoke the @@ -82,6 +82,16 @@ other directory on your system. You can also invoke the
* Changes in Emacs 31.1 * Changes in Emacs 31.1
@@ -45,10 +45,9 @@ index 7367e3ccbd..4c149e41d6 100644
+Follow keyboard focus), Emacs informs Zoom of the text cursor position +Follow keyboard focus), Emacs informs Zoom of the text cursor position
+after every cursor redraw via 'UAZoomChangeFocus'. The zoomed viewport +after every cursor redraw via 'UAZoomChangeFocus'. The zoomed viewport
+automatically tracks the insertion point across window splits and +automatically tracks the insertion point across window splits and
+switches. Completion frameworks (Vertico, Icomplete, Ivy for overlay +switches. Overlay-based completion frameworks and child-frame popup
+candidates; Corfu, Company-box for child frame popups) are also +completions are also tracked: Zoom follows the selected candidate
+tracked: Zoom follows the selected candidate rather than the text +rather than the text cursor during completion.
+cursor during completion.
+ +
+++ +++
** 'line-spacing' now supports specifying spacing above the line. ** 'line-spacing' now supports specifying spacing above the line.
@@ -86,7 +85,7 @@ index 932d209f56..88c9251c18 100644
#endif #endif
static EmacsMenu *dockMenu; static EmacsMenu *dockMenu;
@@ -1081,6 +1086,281 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen) @@ -1081,6 +1086,284 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
} }
@@ -124,6 +123,9 @@ index 932d209f56..88c9251c18 100644
+ return ns_zoom_cached_enabled; + return ns_zoom_cached_enabled;
+} +}
+ +
+/* Note: ns_ax_face_is_selected in the VoiceOver series has identical
+ logic. The duplication is intentional: both series are independent
+ and must compile standalone. */
+/* Identify faces that mark a selected completion candidate. +/* Identify faces that mark a selected completion candidate.
+ Matches vertico-current, corfu-current, icomplete-selected-match, + Matches vertico-current, corfu-current, icomplete-selected-match,
+ ivy-current-match, etc. by checking the face symbol name. + ivy-current-match, etc. by checking the face symbol name.
@@ -368,7 +370,7 @@ index 932d209f56..88c9251c18 100644
static void static void
ns_update_end (struct frame *f) ns_update_end (struct frame *f)
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
@@ -1104,6 +1384,41 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen) @@ -1104,6 +1387,41 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
unblock_input (); unblock_input ();
ns_updating_frame = NULL; ns_updating_frame = NULL;
@@ -410,7 +412,7 @@ index 932d209f56..88c9251c18 100644
} }
static void static void
@@ -3232,6 +3547,45 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors. @@ -3232,6 +3550,45 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
/* Prevent the cursor from being drawn outside the text area. */ /* Prevent the cursor from being drawn outside the text area. */
r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA)); r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA));

View File

@@ -44,7 +44,7 @@ index ea6e7ba4f5..5746e9e9bd 100644
+ +
+ Accessibility virtual elements (macOS / Cocoa only) + Accessibility virtual elements (macOS / Cocoa only)
+ +
+ ========================================================================== */ + ========================================================================= */
+ +
+#ifdef NS_IMPL_COCOA +#ifdef NS_IMPL_COCOA
+@class EmacsView; +@class EmacsView;
@@ -52,11 +52,11 @@ index ea6e7ba4f5..5746e9e9bd 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..5746e9e9bd 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..5746e9e9bd 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
+ +
@@ -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 @@ -7204,6 +7205,432 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
} }
#endif #endif
@@ -208,7 +208,7 @@ index 88c9251c18..9d36de66f9 100644
+ +
+ Accessibility virtual elements (macOS / Cocoa only) + Accessibility virtual elements (macOS / Cocoa only)
+ +
+ ========================================================================== */ + ========================================================================= */
+ +
+#ifdef NS_IMPL_COCOA +#ifdef NS_IMPL_COCOA
+ +
@@ -248,9 +248,9 @@ index 88c9251c18..9d36de66f9 100644
+ return @""; + return @"";
+ +
+ specpdl_ref count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX ();
+ 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);
+ +
@@ -291,7 +291,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 +372,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;
@@ -633,7 +633,7 @@ index 88c9251c18..9d36de66f9 100644
/* ========================================================================== /* ==========================================================================
EmacsView implementation EmacsView implementation
@@ -11657,6 +12084,24 @@ Convert an X font name (XLFD) to an NS font name. @@ -11660,6 +12087,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 +658,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 @@ -11808,6 +12253,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

@@ -30,7 +30,7 @@ diff --git a/src/nsterm.m b/src/nsterm.m
index 9d36de66f9..6256dbc22e 100644 index 9d36de66f9..6256dbc22e 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7625,6 +7625,1121 @@ - (id)accessibilityTopLevelUIElement @@ -7628,6 +7628,1121 @@ - (id)accessibilityTopLevelUIElement
@end @end
@@ -352,7 +352,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]);
@@ -467,7 +467,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)
+ { + {
@@ -516,10 +516,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)
@@ -561,7 +561,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];
@@ -583,7 +583,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

View File

@@ -29,14 +29,14 @@ diff --git a/src/nsterm.m b/src/nsterm.m
index 6256dbc22e..9e0e317237 100644 index 6256dbc22e..9e0e317237 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -8740,6 +8740,612 @@ - (NSRect)accessibilityFrame @@ -8743,6 +8743,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 = @{
@@ -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

@@ -21,7 +21,7 @@ diff --git a/src/nsterm.m b/src/nsterm.m
index 9e0e317237..8aa5b6ac1b 100644 index 9e0e317237..8aa5b6ac1b 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -9346,6 +9346,298 @@ - (NSRect)accessibilityFrame @@ -9349,6 +9349,298 @@ - (NSRect)accessibilityFrame
@end @end

View File

@@ -24,7 +24,7 @@ com.apple.accessibility.api distributed notification.
--- ---
etc/NEWS | 13 ++ etc/NEWS | 13 ++
src/nsterm.m | 474 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/nsterm.m | 474 +++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 475 insertions(+), 12 deletions(-) 2 files changed, 479 insertions(+), 12 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
@@ -54,7 +54,7 @@ diff --git a/src/nsterm.m b/src/nsterm.m
index 8aa5b6ac1b..32eb04acef 100644 index 8aa5b6ac1b..32eb04acef 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1275,7 +1275,7 @@ If a completion candidate is selected (overlay or child frame), @@ -1278,7 +1278,7 @@ If a completion candidate is selected (overlay or child frame),
static void static void
ns_zoom_track_completion (struct frame *f, EmacsView *view) ns_zoom_track_completion (struct frame *f, EmacsView *view)
{ {
@@ -63,7 +63,7 @@ index 8aa5b6ac1b..32eb04acef 100644
return; return;
if (!WINDOWP (f->selected_window)) if (!WINDOWP (f->selected_window))
return; return;
@@ -1393,7 +1393,8 @@ so the visual offset is (ov_line + 1) * line_h from @@ -1396,7 +1396,8 @@ so the visual offset is (ov_line + 1) * line_h from
(zoomCursorUpdated is NO). */ (zoomCursorUpdated is NO). */
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \ #if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
@@ -73,7 +73,7 @@ index 8aa5b6ac1b..32eb04acef 100644
&& !NSIsEmptyRect (view->lastCursorRect)) && !NSIsEmptyRect (view->lastCursorRect))
{ {
NSRect r = view->lastCursorRect; NSRect r = view->lastCursorRect;
@@ -1420,6 +1421,9 @@ so the visual offset is (ov_line + 1) * line_h from @@ -1423,6 +1424,9 @@ so the visual offset is (ov_line + 1) * line_h from
if (view) if (view)
ns_zoom_track_completion (f, view); ns_zoom_track_completion (f, view);
#endif /* NS_IMPL_COCOA */ #endif /* NS_IMPL_COCOA */
@@ -83,7 +83,7 @@ index 8aa5b6ac1b..32eb04acef 100644
} }
static void static void
@@ -3567,7 +3571,7 @@ EmacsView pixels (AppKit, flipped, top-left origin) @@ -3570,7 +3574,7 @@ EmacsView pixels (AppKit, flipped, top-left origin)
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \ #if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
@@ -92,7 +92,7 @@ index 8aa5b6ac1b..32eb04acef 100644
{ {
NSRect windowRect = [view convertRect:r toView:nil]; NSRect windowRect = [view convertRect:r toView:nil];
NSRect screenRect NSRect screenRect
@@ -6723,9 +6727,56 @@ - (void)applicationDidFinishLaunching: (NSNotification *)notification @@ -6726,9 +6730,56 @@ - (void)applicationDidFinishLaunching: (NSNotification *)notification
} }
#endif #endif
@@ -149,7 +149,7 @@ index 8aa5b6ac1b..32eb04acef 100644
- (void)antialiasThresholdDidChange:(NSNotification *)notification - (void)antialiasThresholdDidChange:(NSNotification *)notification
{ {
#ifdef NS_IMPL_COCOA #ifdef NS_IMPL_COCOA
@@ -7628,7 +7679,6 @@ - (id)accessibilityTopLevelUIElement @@ -7631,7 +7682,6 @@ - (id)accessibilityTopLevelUIElement
@@ -157,7 +157,7 @@ index 8aa5b6ac1b..32eb04acef 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 @@ -8744,7 +8794,6 @@ - (NSRect)accessibilityFrame
@end @end
@@ -165,12 +165,12 @@ index 8aa5b6ac1b..32eb04acef 100644
/* =================================================================== /* ===================================================================
EmacsAccessibilityBuffer (Notifications) — AX event dispatch EmacsAccessibilityBuffer (Notifications) — AX event dispatch
@@ -9235,6 +9284,50 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9238,6 +9287,50 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
granularity = ns_ax_text_selection_granularity_line; granularity = ns_ax_text_selection_granularity_line;
} }
+ /* Programmatic jumps that cross a line boundary (]], [[, M-<, + /* Programmatic jumps that cross a line boundary (]], [[, M-<,
+ xref, imenu, ) are discontiguous: the cursor teleported to an + xref, imenu, ...) are discontiguous: the cursor teleported to an
+ arbitrary position, not one sequential step forward/backward. + arbitrary position, not one sequential step forward/backward.
+ Reporting AXTextSelectionDirectionDiscontiguous causes VoiceOver + Reporting AXTextSelectionDirectionDiscontiguous causes VoiceOver
+ to re-anchor its rotor browse cursor at the new + to re-anchor its rotor browse cursor at the new
@@ -179,6 +179,10 @@ index 8aa5b6ac1b..32eb04acef 100644
+ if (!isCtrlNP && granularity == ns_ax_text_selection_granularity_line) + if (!isCtrlNP && granularity == ns_ax_text_selection_granularity_line)
+ direction = ns_ax_text_selection_direction_discontiguous; + direction = ns_ax_text_selection_direction_discontiguous;
+ +
+ /* Until voiceoverSetPoint tracking is added, treat all cursor
+ moves as Emacs-initiated (refined in a later patch). */
+ BOOL emacsMovedCursor = YES;
+
+ /* If Emacs moved the cursor (not VoiceOver), force discontiguous + /* If Emacs moved the cursor (not VoiceOver), force discontiguous
+ so VoiceOver re-anchors its browse cursor to the current + so VoiceOver re-anchors its browse cursor to the current
+ accessibilitySelectedTextRange. This covers all Emacs-initiated + accessibilitySelectedTextRange. This covers all Emacs-initiated
@@ -216,7 +220,7 @@ index 8aa5b6ac1b..32eb04acef 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 +9440,6 @@ - (NSRect)accessibilityFrame @@ -9350,7 +9443,6 @@ - (NSRect)accessibilityFrame
@end @end
@@ -224,7 +228,7 @@ index 8aa5b6ac1b..32eb04acef 100644
/* =================================================================== /* ===================================================================
EmacsAccessibilityInteractiveSpan --- helpers and implementation EmacsAccessibilityInteractiveSpan --- helpers and implementation
=================================================================== */ =================================================================== */
@@ -9683,6 +9775,7 @@ - (void)dealloc @@ -9686,6 +9778,7 @@ - (void)dealloc
[layer release]; [layer release];
#endif #endif
@@ -232,7 +236,7 @@ index 8aa5b6ac1b..32eb04acef 100644
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -11031,6 +11124,32 @@ - (void)windowDidBecomeKey /* for direct calls */ @@ -11034,6 +11127,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
@@ -265,7 +269,7 @@ index 8aa5b6ac1b..32eb04acef 100644
} }
@@ -12268,6 +12387,332 @@ - (int) fullscreenState @@ -12271,6 +12390,332 @@ - (int) fullscreenState
return fs_state; return fs_state;
} }
@@ -285,7 +289,7 @@ index 8aa5b6ac1b..32eb04acef 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)
@@ -319,7 +323,7 @@ index 8aa5b6ac1b..32eb04acef 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))
+ { + {
@@ -431,7 +435,7 @@ index 8aa5b6ac1b..32eb04acef 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))
+ { + {
@@ -440,12 +444,12 @@ index 8aa5b6ac1b..32eb04acef 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];
@@ -598,7 +602,7 @@ index 8aa5b6ac1b..32eb04acef 100644
@end /* EmacsView */ @end /* EmacsView */
@@ -14264,12 +14709,17 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with @@ -14267,12 +14712,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

@@ -11,9 +11,9 @@ enabled, and known limitations. Use @xref for cross-reference at
sentence start. Correct description of ns-accessibility-enabled sentence start. Correct description of ns-accessibility-enabled
default: initial value is nil, set automatically at startup. default: initial value is nil, set automatically at startup.
--- ---
doc/emacs/macos.texi | 76 ++++++++++++++++++++++++++++++++++++++++++++ doc/emacs/macos.texi | 77 ++++++++++++++++++++++++++++++++++++++++++++
src/nsterm.m | 10 ++++-- src/nsterm.m | 10 ++++--
2 files changed, 83 insertions(+), 3 deletions(-) 2 files changed, 84 insertions(+), 3 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 6bd334f48e..8d4a7825d8 100644 index 6bd334f48e..8d4a7825d8 100644
@@ -27,7 +27,7 @@ index 6bd334f48e..8d4a7825d8 100644
* GNUstep Support:: Details on status of GNUstep support. * GNUstep Support:: Details on status of GNUstep support.
@end menu @end menu
@@ -272,6 +273,81 @@ and return the result as a string. You can also use the Lisp function @@ -272,6 +273,82 @@ and return the result as a string. You can also use the Lisp function
services and receive the results back. Note that you may need to services and receive the results back. Note that you may need to
restart Emacs to access newly-available services. restart Emacs to access newly-available services.
@@ -101,7 +101,8 @@ index 6bd334f48e..8d4a7825d8 100644
+ +
+ This support is available only on the Cocoa build. GNUstep has a + This support is available only on the Cocoa build. GNUstep has a
+different accessibility model and is not yet supported. +different accessibility model and is not yet supported.
+Block-style cursors are handled +
+ Block-style cursors are handled
+correctly: character navigation announces the character at the cursor +correctly: character navigation announces the character at the cursor
+position, not the character before it. +position, not the character before it.
+ +
@@ -113,7 +114,7 @@ diff --git a/src/nsterm.m b/src/nsterm.m
index 32eb04acef..8e5cc7e1d7 100644 index 32eb04acef..8e5cc7e1d7 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -14710,9 +14710,13 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with @@ -14713,9 +14713,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,11 +1,11 @@
From affdaf60d28ad4d9836c6505216e19599b31c437 Mon Sep 17 00:00:00 2001 From affdaf60d28ad4d9836c6505216e19599b31c437 Mon Sep 17 00:00:00 2001
From: Daneel <daneel@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 7/8] ns: announce overlay completion candidates for VoiceOver Subject: [PATCH 7/8] ns: announce overlay completion candidates for VoiceOver
Completion frameworks such as Vertico, Ivy, and Icomplete render Overlay-based completion frameworks render candidates via overlay
candidates via overlay before-string/after-string properties. Without before-string/after-string properties (e.g., Vertico, Icomplete,
this change VoiceOver cannot read overlay-based completion UIs. Ivy). Without this change VoiceOver cannot read these completion UIs.
* src/nsterm.m (ns_ax_face_is_selected): New static function; matches * src/nsterm.m (ns_ax_face_is_selected): New static function; matches
'current', 'selected', 'selection' in face symbol names. 'current', 'selected', 'selection' in face symbol names.
@@ -18,8 +18,8 @@ ValueChanged; keep overlay_modiff out of ensureTextCache to prevent a
race where an AX query consumes the change before notification. race where an AX query consumes the change before notification.
--- ---
src/nsterm.h | 1 + src/nsterm.h | 1 +
src/nsterm.m | 348 ++++++++++++++++++++++++++++++++++++++++++++------- src/nsterm.m | 352 ++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 307 insertions(+), 42 deletions(-) 2 files changed, 311 insertions(+), 42 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 5746e9e9bd..21a93bc799 100644 index 5746e9e9bd..21a93bc799 100644
@@ -37,10 +37,13 @@ diff --git a/src/nsterm.m b/src/nsterm.m
index 8e5cc7e1d7..8ef344d9fe 100644 index 8e5cc7e1d7..8ef344d9fe 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) @@ -7266,11 +7266,158 @@ Accessibility virtual elements (macOS / Cocoa only)
/* ---- Helper: extract buffer text for accessibility ---- */ /* ---- Helper: extract buffer text for accessibility ---- */
+/* Note: ns_zoom_face_is_selected in the Zoom series has identical
+ logic. The duplication is intentional: both series are independent
+ and must compile standalone. */
+/* Return true if FACE is or contains a face symbol whose name +/* Return true if FACE is or contains a face symbol whose name
+ includes "current" or "selected", indicating a highlighted + includes "current" or "selected", indicating a highlighted
+ completion candidate. Works for vertico-current, + completion candidate. Works for vertico-current,
@@ -185,6 +188,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
+ +
+ return nil; + return nil;
+} +}
+
/* 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
@@ -193,7 +197,7 @@ index 8e5cc7e1d7..8ef344d9fe 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 +7489,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
@@ -202,7 +206,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
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 +7570,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
@@ -211,7 +215,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
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 @@ -7899,6 +8045,7 @@ @implementation EmacsAccessibilityBuffer
@synthesize cachedOverlayModiff; @synthesize cachedOverlayModiff;
@synthesize cachedTextStart; @synthesize cachedTextStart;
@synthesize cachedModiff; @synthesize cachedModiff;
@@ -219,7 +223,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
@synthesize cachedPoint; @synthesize cachedPoint;
@synthesize cachedMarkActive; @synthesize cachedMarkActive;
@synthesize cachedCompletionAnnouncement; @synthesize cachedCompletionAnnouncement;
@@ -7993,7 +8137,7 @@ - (void)ensureTextCache @@ -7996,7 +8143,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
@@ -228,7 +232,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
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,34 @@ - (void)ensureTextCache @@ -8008,25 +8155,34 @@ - (void)ensureTextCache
if (!b) if (!b)
return; return;
@@ -280,7 +284,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
&& cachedTextStart == BUF_BEGV (b) && cachedTextStart == BUF_BEGV (b)
&& pt >= cachedTextStart && pt >= cachedTextStart
&& (textLen == 0 && (textLen == 0
@@ -8039,7 +8192,7 @@ included in the cached AX text (it is handled separately via @@ -8042,7 +8198,7 @@ included in the cached AX text (it is handled separately via
{ {
[cachedText release]; [cachedText release];
cachedText = [text retain]; cachedText = [text retain];
@@ -289,7 +293,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
cachedTextStart = start; cachedTextStart = start;
if (visibleRuns) if (visibleRuns)
@@ -8051,9 +8204,9 @@ included in the cached AX text (it is handled separately via @@ -8054,9 +8210,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
@@ -302,7 +306,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
enters this code. */ enters this code. */
if (lineStartOffsets) if (lineStartOffsets)
xfree (lineStartOffsets); xfree (lineStartOffsets);
@@ -8108,7 +8261,7 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos @@ -8111,7 +8267,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
@@ -311,7 +315,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
NSUInteger lo = 0, hi = visibleRunCount; NSUInteger lo = 0, hi = visibleRunCount;
while (lo < hi) while (lo < hi)
{ {
@@ -8157,10 +8310,10 @@ by run length (visible window), not total buffer size. */ @@ -8160,10 +8316,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
@@ -324,7 +328,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
@synchronized (self) @synchronized (self)
{ {
if (visibleRunCount == 0) if (visibleRunCount == 0)
@@ -8202,7 +8355,7 @@ the slow path (composed character sequence walk), which is @@ -8205,7 +8361,7 @@ the slow path (composed character sequence walk), which is
return cp; return cp;
} }
} }
@@ -333,7 +337,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
if (lo > 0) if (lo > 0)
{ {
ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1];
@@ -8224,7 +8377,7 @@ the slow path (composed character sequence walk), which is @@ -8227,7 +8383,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
@@ -342,7 +346,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
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 +8723,50 @@ - (NSInteger)accessibilityInsertionPointLineNumber @@ -8573,6 +8729,50 @@ - (NSInteger)accessibilityInsertionPointLineNumber
return [self lineForAXIndex:point_idx]; return [self lineForAXIndex:point_idx];
} }
@@ -393,7 +397,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
- (NSRange)accessibilityRangeForLine:(NSInteger)line - (NSRange)accessibilityRangeForLine:(NSInteger)line
{ {
if (![NSThread isMainThread]) if (![NSThread isMainThread])
@@ -8792,7 +8989,7 @@ - (NSRect)accessibilityFrame @@ -8795,7 +8995,7 @@ - (NSRect)accessibilityFrame
/* =================================================================== /* ===================================================================
@@ -402,7 +406,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
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 +9004,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point @@ -8810,7 +9010,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
if (point > self.cachedPoint if (point > self.cachedPoint
&& point - self.cachedPoint == 1) && point - self.cachedPoint == 1)
{ {
@@ -411,7 +415,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
[self invalidateTextCache]; [self invalidateTextCache];
[self ensureTextCache]; [self ensureTextCache];
if (cachedText) if (cachedText)
@@ -8826,7 +9023,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point @@ -8829,7 +9029,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
@@ -420,7 +424,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
self.cachedPoint = point; self.cachedPoint = point;
NSDictionary *change = @{ NSDictionary *change = @{
@@ -9220,16 +9417,83 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9223,16 +9423,83 @@ - (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) --- */
@@ -508,7 +512,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
{ {
ptrdiff_t oldPoint = self.cachedPoint; ptrdiff_t oldPoint = self.cachedPoint;
BOOL oldMarkActive = self.cachedMarkActive; BOOL oldMarkActive = self.cachedMarkActive;
@@ -12403,7 +12667,7 @@ - (int) fullscreenState @@ -12406,7 +12673,7 @@ - (int) fullscreenState
if (WINDOW_LEAF_P (w)) if (WINDOW_LEAF_P (w))
{ {
@@ -517,7 +521,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
EmacsAccessibilityBuffer *elem EmacsAccessibilityBuffer *elem
= [existing objectForKey:[NSValue valueWithPointer:w]]; = [existing objectForKey:[NSValue valueWithPointer:w]];
if (!elem) if (!elem)
@@ -12437,7 +12701,7 @@ - (int) fullscreenState @@ -12440,7 +12707,7 @@ - (int) fullscreenState
} }
else else
{ {
@@ -526,7 +530,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
Lisp_Object child = w->contents; Lisp_Object child = w->contents;
while (!NILP (child)) while (!NILP (child))
{ {
@@ -12549,7 +12813,7 @@ - (void)postAccessibilityUpdates @@ -12552,7 +12819,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
@@ -535,7 +539,7 @@ index 8e5cc7e1d7..8ef344d9fe 100644
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe); Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
if (!EQ (curRoot, lastRootWindow)) if (!EQ (curRoot, lastRootWindow))
{ {
@@ -12558,12 +12822,12 @@ - (void)postAccessibilityUpdates @@ -12561,12 +12828,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

View File

@@ -1,13 +1,13 @@
From 1e3d3919fd41e4480a02190fb89bee1ef8107d62 Mon Sep 17 00:00:00 2001 From 1e3d3919fd41e4480a02190fb89bee1ef8107d62 Mon Sep 17 00:00:00 2001
From: Daneel <daneel@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 8/8] ns: announce child frame completion candidates for Subject: [PATCH 8/8] ns: announce child frame completion candidates for
VoiceOver VoiceOver
Child frame popups (Corfu, Company-mode child frames) render completion Child-frame completion frameworks render candidates in a separate
candidates in a separate frame whose buffer is not accessible via the frame whose buffer is not accessible via the minibuffer overlay path
minibuffer overlay path. This patch scans child frame buffers for (e.g., Corfu, Company-box). This patch scans child frame buffers
selected candidates and announces them via VoiceOver. for selected candidates and announces them via VoiceOver.
* src/nsterm.h (EmacsView): Add childFrameLastBuffer, childFrameLastModiff, * src/nsterm.h (EmacsView): Add childFrameLastBuffer, childFrameLastModiff,
childFrameLastCandidate, childFrameCompletionActive, lastEchoCharsModiff childFrameLastCandidate, childFrameCompletionActive, lastEchoCharsModiff
@@ -34,7 +34,7 @@ area announcements.
etc/NEWS | 25 ++- etc/NEWS | 25 ++-
src/nsterm.h | 21 ++ src/nsterm.h | 21 ++
src/nsterm.m | 514 +++++++++++++++++++++++++++++++++++++++---- src/nsterm.m | 514 +++++++++++++++++++++++++++++++++++++++----
4 files changed, 510 insertions(+), 64 deletions(-) 4 files changed, 510 insertions(+), 68 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 8d4a7825d8..03a657f970 100644 index 8d4a7825d8..03a657f970 100644
@@ -167,7 +167,7 @@ diff --git a/src/nsterm.m b/src/nsterm.m
index 8ef344d9fe..1acb64630a 100644 index 8ef344d9fe..1acb64630a 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1275,7 +1275,13 @@ If a completion candidate is selected (overlay or child frame), @@ -1278,7 +1278,13 @@ If a completion candidate is selected (overlay or child frame),
static void static void
ns_zoom_track_completion (struct frame *f, EmacsView *view) ns_zoom_track_completion (struct frame *f, EmacsView *view)
{ {
@@ -182,7 +182,7 @@ index 8ef344d9fe..1acb64630a 100644
return; return;
if (!WINDOWP (f->selected_window)) if (!WINDOWP (f->selected_window))
return; return;
@@ -1393,7 +1399,7 @@ so the visual offset is (ov_line + 1) * line_h from @@ -1396,7 +1402,7 @@ so the visual offset is (ov_line + 1) * line_h from
(zoomCursorUpdated is NO). */ (zoomCursorUpdated is NO). */
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \ #if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
@@ -191,7 +191,7 @@ index 8ef344d9fe..1acb64630a 100644
&& ns_zoom_enabled_p () && ns_zoom_enabled_p ()
&& !NSIsEmptyRect (view->lastCursorRect)) && !NSIsEmptyRect (view->lastCursorRect))
{ {
@@ -3571,7 +3577,7 @@ EmacsView pixels (AppKit, flipped, top-left origin) @@ -3574,7 +3580,7 @@ EmacsView pixels (AppKit, flipped, top-left origin)
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \ #if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
@@ -200,7 +200,7 @@ index 8ef344d9fe..1acb64630a 100644
{ {
NSRect windowRect = [view convertRect:r toView:nil]; NSRect windowRect = [view convertRect:r toView:nil];
NSRect screenRect NSRect screenRect
@@ -7407,6 +7413,117 @@ visual line index for Zoom (skip whitespace-only lines @@ -7413,6 +7419,117 @@ visual line index for Zoom (skip whitespace-only lines
return nil; return nil;
} }
@@ -318,7 +318,7 @@ index 8ef344d9fe..1acb64630a 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 @@ -7446,9 +7563,13 @@ visual line index for Zoom (skip whitespace-only lines
return @""; return @"";
specpdl_ref count = SPECPDL_INDEX (); specpdl_ref count = SPECPDL_INDEX ();
@@ -326,14 +326,13 @@ index 8ef344d9fe..1acb64630a 100644
+ 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);
@@ -8605,6 +8726,11 @@ - (void)setAccessibilitySelectedTextRange:(NSRange)range @@ -8611,6 +8732,11 @@ - (void)setAccessibilitySelectedTextRange:(NSRange)range
[self ensureTextCache]; [self ensureTextCache];
@@ -345,7 +344,7 @@ index 8ef344d9fe..1acb64630a 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 /* Ensure block_input is always matched by unblock_input even if
@@ -9053,20 +9179,38 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point @@ -9059,20 +9185,38 @@ - (void)postFocusedCursorNotification:(ptrdiff_t)point
&& granularity && granularity
== ns_ax_text_selection_granularity_character); == ns_ax_text_selection_granularity_character);
@@ -394,7 +393,7 @@ index 8ef344d9fe..1acb64630a 100644
ns_ax_post_notification_with_info ( ns_ax_post_notification_with_info (
self, self,
NSAccessibilitySelectedTextChangedNotification, NSAccessibilitySelectedTextChangedNotification,
@@ -9166,12 +9310,17 @@ user expectation ("w" jumps to next word and reads it). */ @@ -9172,12 +9316,17 @@ user expectation ("w" jumps to next word and reads it). */
} }
} }
@@ -407,7 +406,7 @@ index 8ef344d9fe..1acb64630a 100644
+ - C-n/C-p: SelectedTextChanged carries granularity=line, but + - C-n/C-p: SelectedTextChanged carries granularity=line, but
+ VoiceOver processes those keystrokes specially and may not + VoiceOver processes those keystrokes specially and may not
+ produce speech; the explicit announcement is the reliable path. + produce speech; the explicit announcement is the reliable path.
+ - Discontiguous jumps (]], M-<, xref, imenu, ): granularity=line + - Discontiguous jumps (]], M-<, xref, imenu, ...): granularity=line
+ in the notification is omitted (see above) so VoiceOver will + in the notification is omitted (see above) so VoiceOver will
+ not announce automatically; this explicit announcement fills + not announce automatically; this explicit announcement fills
+ the gap. + the gap.
@@ -417,7 +416,7 @@ index 8ef344d9fe..1acb64630a 100644
if (cachedText if (cachedText
&& granularity == ns_ax_text_selection_granularity_line) && granularity == ns_ax_text_selection_granularity_line)
{ {
@@ -9236,6 +9385,11 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b @@ -9242,6 +9391,11 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
block_input (); block_input ();
specpdl_ref count2 = SPECPDL_INDEX (); specpdl_ref count2 = SPECPDL_INDEX ();
@@ -429,7 +428,7 @@ index 8ef344d9fe..1acb64630a 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)
@@ -9412,12 +9566,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9418,12 +9572,29 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
if (!b) if (!b)
return; return;
@@ -437,7 +436,7 @@ index 8ef344d9fe..1acb64630a 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;
@@ -459,7 +458,7 @@ index 8ef344d9fe..1acb64630a 100644
if (modiff != self.cachedModiff) if (modiff != self.cachedModiff)
{ {
self.cachedModiff = modiff; self.cachedModiff = modiff;
@@ -9431,6 +9602,7 @@ Text property changes (e.g. face updates from @@ -9437,6 +9608,7 @@ Text property changes (e.g. face updates from
{ {
self.cachedCharsModiff = chars_modiff; self.cachedCharsModiff = chars_modiff;
[self postTextChangedNotification:point]; [self postTextChangedNotification:point];
@@ -467,7 +466,7 @@ index 8ef344d9fe..1acb64630a 100644
} }
} }
@@ -9453,8 +9625,15 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property @@ -9459,8 +9631,15 @@ 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.
@@ -485,7 +484,7 @@ index 8ef344d9fe..1acb64630a 100644
goto skip_overlay_scan; goto skip_overlay_scan;
int selected_line = -1; int selected_line = -1;
@@ -9500,7 +9679,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property @@ -9506,7 +9685,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
self.cachedPoint = point; self.cachedPoint = point;
self.cachedMarkActive = markActive; self.cachedMarkActive = markActive;
@@ -505,7 +504,7 @@ index 8ef344d9fe..1acb64630a 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;
@@ -9512,6 +9702,7 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property @@ -9518,6 +9708,7 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
/* --- Granularity detection --- */ /* --- Granularity detection --- */
NSInteger granularity = ns_ax_text_selection_granularity_unknown; NSInteger granularity = ns_ax_text_selection_granularity_unknown;
@@ -513,7 +512,7 @@ index 8ef344d9fe..1acb64630a 100644
[self ensureTextCache]; [self ensureTextCache];
if (cachedText && oldPoint > 0) if (cachedText && oldPoint > 0)
{ {
@@ -9526,7 +9717,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property @@ -9532,7 +9723,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
NSRange newLine = [cachedText lineRangeForRange: NSRange newLine = [cachedText lineRangeForRange:
NSMakeRange (newIdx, 0)]; NSMakeRange (newIdx, 0)];
if (oldLine.location != newLine.location) if (oldLine.location != newLine.location)
@@ -522,7 +521,7 @@ index 8ef344d9fe..1acb64630a 100644
+ granularity = ns_ax_text_selection_granularity_line; + granularity = ns_ax_text_selection_granularity_line;
+ /* Detect single adjacent-line move while oldLine/newLine + /* Detect single adjacent-line move while oldLine/newLine
+ are in scope. Any command that steps exactly one line --- + are in scope. Any command that steps exactly one line ---
+ C-n/C-p, evil j/k, outline-next-heading, etc. --- is + C-n/C-p, outline-next-heading, etc. --- is
+ sequential. Multi-line teleports (]], M-<, xref, ...) are + sequential. Multi-line teleports (]], M-<, xref, ...) are
+ not adjacent and will be marked discontiguous below. + not adjacent and will be marked discontiguous below.
+ Detected structurally: no package-specific code needed. */ + Detected structurally: no package-specific code needed. */
@@ -533,7 +532,7 @@ index 8ef344d9fe..1acb64630a 100644
else else
{ {
NSUInteger dist = (newIdx > oldIdx NSUInteger dist = (newIdx > oldIdx
@@ -9548,34 +9750,23 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property @@ -9554,38 +9756,23 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
granularity = ns_ax_text_selection_granularity_line; granularity = ns_ax_text_selection_granularity_line;
} }
@@ -545,13 +544,19 @@ index 8ef344d9fe..1acb64630a 100644
- accessibilitySelectedTextRange rather than advancing linearly - accessibilitySelectedTextRange rather than advancing linearly
- from its previous internal position. */ - from its previous internal position. */
- if (!isCtrlNP && granularity == ns_ax_text_selection_granularity_line) - if (!isCtrlNP && granularity == ns_ax_text_selection_granularity_line)
- direction = ns_ax_text_selection_direction_discontiguous;
-
- /* Until voiceoverSetPoint tracking is added, treat all cursor
- moves as Emacs-initiated (refined in a later patch). */
- BOOL emacsMovedCursor = YES;
-
+ +
+ /* Multi-line teleports are discontiguous; single adjacent-line + /* Multi-line teleports are discontiguous; single adjacent-line
+ steps stay sequential. */ + steps stay sequential. */
+ if (!isCtrlNP && !singleLineMove + if (!isCtrlNP && !singleLineMove
+ && granularity == ns_ax_text_selection_granularity_line) + && granularity == ns_ax_text_selection_granularity_line)
direction = ns_ax_text_selection_direction_discontiguous; + direction = ns_ax_text_selection_direction_discontiguous;
+
- /* If Emacs moved the cursor (not VoiceOver), force discontiguous - /* If Emacs moved the cursor (not VoiceOver), force discontiguous
- so VoiceOver re-anchors its browse cursor to the current - so VoiceOver re-anchors its browse cursor to the current
- accessibilitySelectedTextRange. This covers all Emacs-initiated - accessibilitySelectedTextRange. This covers all Emacs-initiated
@@ -581,7 +586,7 @@ index 8ef344d9fe..1acb64630a 100644
{ {
NSWindow *win = [self.emacsView window]; NSWindow *win = [self.emacsView window];
if (win) if (win)
@@ -9734,6 +9925,13 @@ - (NSRect)accessibilityFrame @@ -9740,6 +9931,13 @@ - (NSRect)accessibilityFrame
if (vis_start >= vis_end) if (vis_start >= vis_end)
return @[]; return @[];
@@ -595,7 +600,7 @@ index 8ef344d9fe..1acb64630a 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);
@@ -10040,6 +10238,10 @@ - (void)dealloc @@ -10046,6 +10244,10 @@ - (void)dealloc
#endif #endif
[accessibilityElements release]; [accessibilityElements release];
@@ -606,7 +611,7 @@ index 8ef344d9fe..1acb64630a 100644
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -11489,6 +11691,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f @@ -11495,6 +11697,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
windowClosing = NO; windowClosing = NO;
processingCompose = NO; processingCompose = NO;
@@ -616,7 +621,7 @@ index 8ef344d9fe..1acb64630a 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;
@@ -12797,6 +13002,161 @@ - (id)accessibilityFocusedUIElement @@ -12803,6 +13008,161 @@ - (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. */
@@ -778,7 +783,7 @@ index 8ef344d9fe..1acb64630a 100644
- (void)postAccessibilityUpdates - (void)postAccessibilityUpdates
{ {
NSTRACE ("[EmacsView postAccessibilityUpdates]"); NSTRACE ("[EmacsView postAccessibilityUpdates]");
@@ -12807,11 +13167,69 @@ - (void)postAccessibilityUpdates @@ -12813,11 +13173,69 @@ - (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

View File

@@ -88,7 +88,9 @@ ARCHITECTURE
PERFORMANCE PERFORMANCE
----------- -----------
ns-accessibility-enabled (DEFVAR_BOOL, default t): ns-accessibility-enabled (DEFVAR_BOOL, default nil):
Set automatically at startup when macOS Zoom or an assistive
technology (VoiceOver, Switch Control) is detected.
When nil, no virtual elements are built, no notifications are When nil, no virtual elements are built, no notifications are
posted, and ns_draw_window_cursor skips the cursor rect store. posted, and ns_draw_window_cursor skips the cursor rect store.
Zero overhead for users who do not use assistive technology. Zero overhead for users who do not use assistive technology.
@@ -117,7 +119,7 @@ KNOWN LIMITATIONS
- Overlay face matching: string containment ("current", "selected") - Overlay face matching: string containment ("current", "selected")
- GNUstep excluded (#ifdef NS_IMPL_COCOA) - GNUstep excluded (#ifdef NS_IMPL_COCOA)
- No multi-frame coordination - No multi-frame coordination
- Child frame static lastCandidate leaks at exit (minor) - Child frame childFrameLastCandidate (per-view ivar, freed in dealloc)
TESTING TESTING

View File

@@ -112,7 +112,8 @@ PASS — Folded text NOT read, unfolded text read correctly.
11. ERT — ns-accessibility-enabled Variable 11. ERT — ns-accessibility-enabled Variable
-------------------------------------------- --------------------------------------------
PASS — ns-accessibility-enabled bound, defaults to t. PASS — ns-accessibility-enabled bound, defaults to nil.
Set automatically to t when AT is detected at startup.
12. VoiceOver — Overlay Completion (Patch 0007) 12. VoiceOver — Overlay Completion (Patch 0007)
------------------------------------------------ ------------------------------------------------
@@ -143,3 +144,10 @@ PASS — When set to nil:
----------------- -----------------
PASS — Texinfo node accessible via C-h i g (emacs)VoiceOver. PASS — Texinfo node accessible via C-h i g (emacs)VoiceOver.
etc/NEWS entry present and accurate. etc/NEWS entry present and accurate.
16. Patch Compilation Order
---------------------------
VERIFIED — Each patch 0001-0008 declares all variables before use.
- 0005: emacsMovedCursor declared locally (BOOL YES) before use.
- 0008: replaces with voiceoverSetPoint-based detection.
- No forward references to code in later patches.

50
patches/fix_0008.py Normal file
View File

@@ -0,0 +1,50 @@
import sys
with open('0008-ns-announce-child-frame-completion-candidates-for-Vo.patch', 'r') as f:
lines = f.readlines()
# Find and fix the problematic hunk
new_lines = []
i = 0
while i < len(lines):
line = lines[i]
# Skip lines 555-562 (0-indexed: 554-561) - the "If Emacs moved" DELETE block
if i == 554:
# Skip 8 lines (555-562)
i += 8
continue
# Skip lines 568-575 (0-indexed: 567-574) after adjustment (now 559-566)
# After removing 8 lines, line 568 is now 560 in 0-indexed terms: 567-8=559
if i == 559:
# Skip 8 lines (568-575 became 560-567)
i += 8
continue
# Change context lines to ADD lines
# Line 566 (0-indexed 565, after adjustments 565-8=557)
if i == 557:
# Change the context line to ADD
if line.startswith(' '):
line = '+' + line[1:]
# Line 567 (0-indexed 566, after adjustments 558)
if i == 558:
if line.startswith(' '):
line = '+' + line[1:]
# Line 581 (0-indexed 580, after adjustments: 580-16=564)
if i == 564:
if line.startswith(' '):
line = '+' + line[1:]
# Line 582 (0-indexed 581, after adjustments: 565)
if i == 565:
if line.startswith(' '):
line = '+' + line[1:]
new_lines.append(line)
i += 1
with open('0008-ns-announce-child-frame-completion-candidates-for-Vo.patch', 'w') as f:
f.writelines(new_lines)
print("Done")