patches: fix O(overlays) performance regression

Performance issue: editing large files (>~10KB, >2000 lines) caused
progressive slowdown regardless of VoiceOver status.

Root causes:
1. ns_zoom_find_overlay_candidate_line: called Foverlays_in on the
   entire visible buffer range on every redisplay when UAZoomEnabled().
   In files with many overlays (font-lock, hl-line, show-paren etc.)
   this was O(overlays) Lisp work per keystroke.

2. postAccessibilityNotificationsForFrame: when ns-accessibility-enabled
   is non-nil, checked BUF_OVERLAY_MODIFF every redisplay. font-lock
   bumps this on every redraw, triggering ns_ax_selected_overlay_text
   (another O(overlays) scan) for non-minibuffer windows.

Fix: Both scans now guard with MINI_WINDOW_P check. Overlay completion
frameworks (Vertico, Icomplete, Ivy) only display candidates in
minibuffer windows --- no completion framework puts selected-face
overlays in normal editing buffers. For non-minibuffer windows both
functions return immediately with zero Lisp calls.

Additionally: ns_zoom_find_child_frame_candidate is skipped when
f->child_frame_list is nil (no child frames = no Corfu popup).
This commit is contained in:
2026-03-01 04:26:12 +01:00
parent 84eb777065
commit 3d2fa7a54e
9 changed files with 87 additions and 69 deletions

View File

@@ -1,4 +1,4 @@
From 54cae6f8afe265c12cffaef121bad32bc165a777 Mon Sep 17 00:00:00 2001 From 19ab5ca41d38959b8f92ae2e1b7f724972515c3d 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 1/9] ns: integrate with macOS Zoom for cursor tracking Subject: [PATCH 1/9] ns: integrate with macOS Zoom for cursor tracking
@@ -38,8 +38,8 @@ window splits, switches (C-x o), and completion frameworks.
--- ---
etc/NEWS | 11 ++ etc/NEWS | 11 ++
src/nsterm.h | 6 ++ src/nsterm.h | 6 ++
src/nsterm.m | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nsterm.m | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 297 insertions(+) 3 files changed, 306 insertions(+)
diff --git a/etc/NEWS b/etc/NEWS diff --git a/etc/NEWS b/etc/NEWS
index ef36df5..80661a9 100644 index ef36df5..80661a9 100644
@@ -81,10 +81,10 @@ index 7c1ee4c..ea6e7ba 100644
} }
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 74e4ad5..2dd79ca 100644 index 74e4ad5..eee4cd9 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1081,6 +1081,209 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen) @@ -1081,6 +1081,218 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
} }
@@ -105,6 +105,13 @@ index 74e4ad5..2dd79ca 100644
+static int +static int
+ns_zoom_find_overlay_candidate_line (struct window *w) +ns_zoom_find_overlay_candidate_line (struct window *w)
+{ +{
+ /* Overlay completion frameworks (Vertico, Icomplete, Ivy) place
+ candidates as overlay strings in the minibuffer only. Scanning
+ overlays in large normal buffers causes O(overlays) work per
+ redisplay --- return immediately for non-minibuffer windows. */
+ if (!MINI_WINDOW_P (w))
+ return -1;
+
+ struct buffer *b = XBUFFER (w->contents); + struct buffer *b = XBUFFER (w->contents);
+ ptrdiff_t beg = marker_position (w->start); + ptrdiff_t beg = marker_position (w->start);
+ ptrdiff_t end = BUF_ZV (b); + ptrdiff_t end = BUF_ZV (b);
@@ -259,7 +266,9 @@ index 74e4ad5..2dd79ca 100644
+ +
+ /* 2. Check child frame completions (Corfu, Company-box). */ + /* 2. Check child frame completions (Corfu, Company-box). */
+ struct frame *cf = NULL; + struct frame *cf = NULL;
+ int cf_line = ns_zoom_find_child_frame_candidate (f, &cf); + int cf_line = -1;
+ if (!NILP (f->child_frame_list))
+ cf_line = ns_zoom_find_child_frame_candidate (f, &cf);
+ if (cf_line >= 0 && cf) + if (cf_line >= 0 && cf)
+ { + {
+ EmacsView *cv = FRAME_NS_VIEW (cf); + EmacsView *cv = FRAME_NS_VIEW (cf);
@@ -294,7 +303,7 @@ index 74e4ad5..2dd79ca 100644
static void static void
ns_update_end (struct frame *f) ns_update_end (struct frame *f)
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
@@ -1104,6 +1307,41 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen) @@ -1104,6 +1316,41 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
unblock_input (); unblock_input ();
ns_updating_frame = NULL; ns_updating_frame = NULL;
@@ -336,7 +345,7 @@ index 74e4ad5..2dd79ca 100644
} }
static void static void
@@ -3232,6 +3470,45 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors. @@ -3232,6 +3479,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));
@@ -382,7 +391,7 @@ index 74e4ad5..2dd79ca 100644
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -8321,6 +8598,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f @@ -8321,6 +8607,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
windowClosing = NO; windowClosing = NO;
processingCompose = NO; processingCompose = NO;

View File

@@ -1,4 +1,4 @@
From 0e9726214f1081b8c4f23451ada5fc956042938b Mon Sep 17 00:00:00 2001 From 34602bfe4251677ac922f35cf02b3274c457d1d7 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
@@ -188,7 +188,7 @@ index ea6e7ba..6e830de 100644
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 2dd79ca..b4437f1 100644 index eee4cd9..28ee1d5 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)
@@ -199,7 +199,7 @@ index 2dd79ca..b4437f1 100644
#include "systime.h" #include "systime.h"
#include "character.h" #include "character.h"
#include "xwidget.h" #include "xwidget.h"
@@ -7133,6 +7134,430 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg @@ -7142,6 +7143,430 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
} }
#endif #endif
@@ -630,7 +630,7 @@ index 2dd79ca..b4437f1 100644
/* ========================================================================== /* ==========================================================================
EmacsView implementation EmacsView implementation
@@ -11592,6 +12017,28 @@ Convert an X font name (XLFD) to an NS font name. @@ -11601,6 +12026,28 @@ 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");
@@ -659,7 +659,7 @@ index 2dd79ca..b4437f1 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));
@@ -11740,6 +12187,15 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with @@ -11749,6 +12196,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 5d8811666c12f236e8b3ebae8a44520085aa938e Mon Sep 17 00:00:00 2001 From 76f92d7c652350db9a7c0ded206c2e167094378b 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
@@ -22,10 +22,10 @@ line-by-line navigation, word/character announcements.
1 file changed, 1097 insertions(+) 1 file changed, 1097 insertions(+)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index b4437f1..ef645e6 100644 index 28ee1d5..c752bed 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7555,6 +7555,1103 @@ - (id)accessibilityTopLevelUIElement @@ -7564,6 +7564,1103 @@ - (id)accessibilityTopLevelUIElement
@end @end

View File

@@ -1,4 +1,4 @@
From c2caac09ebe9bf816059c752a2f4f32e39347b54 Mon Sep 17 00:00:00 2001 From c3d8dd23094bf49eeba5beac414cfd4321011256 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
@@ -24,10 +24,10 @@ region selection feedback, completion popups, mode-line reading.
1 file changed, 545 insertions(+) 1 file changed, 545 insertions(+)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index ef645e6..41e8d4b 100644 index c752bed..d7e6660 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -8652,6 +8652,551 @@ - (NSRect)accessibilityFrame @@ -8661,6 +8661,551 @@ - (NSRect)accessibilityFrame
@end @end

View File

@@ -1,4 +1,4 @@
From 5d254e28a56e023b9bf1e46cf061b9d596f49b02 Mon Sep 17 00:00:00 2001 From 19a7509084e919d229267f835d86c31d97fb9784 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
@@ -17,10 +17,10 @@ Tested on macOS 14. Verified: Tab-cycling through org-mode links,
1 file changed, 286 insertions(+) 1 file changed, 286 insertions(+)
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 41e8d4b..4692a58 100644 index d7e6660..7fc542b 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -9197,6 +9197,292 @@ - (NSRect)accessibilityFrame @@ -9206,6 +9206,292 @@ - (NSRect)accessibilityFrame
@end @end

View File

@@ -1,4 +1,4 @@
From 4c7ddcb5e6f5905efefa55cb7188667744bd3d0e Mon Sep 17 00:00:00 2001 From ef42812ff2e18ac2492a679a47da350ace8fab54 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
@@ -51,10 +51,10 @@ index 80661a9..2b1f9e6 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.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 4692a58..93da88a 100644 index 7fc542b..c35db76 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1343,6 +1343,9 @@ so the visual offset is (ov_line + 1) * line_h from @@ -1352,6 +1352,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 */
@@ -64,7 +64,7 @@ index 4692a58..93da88a 100644
} }
static void static void
@@ -7558,7 +7561,6 @@ - (id)accessibilityTopLevelUIElement @@ -7567,7 +7570,6 @@ - (id)accessibilityTopLevelUIElement
@@ -72,7 +72,7 @@ index 4692a58..93da88a 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,
@@ -8653,7 +8655,6 @@ - (NSRect)accessibilityFrame @@ -8662,7 +8664,6 @@ - (NSRect)accessibilityFrame
@end @end
@@ -80,7 +80,7 @@ index 4692a58..93da88a 100644
/* =================================================================== /* ===================================================================
EmacsAccessibilityBuffer (Notifications) — AX event dispatch EmacsAccessibilityBuffer (Notifications) — AX event dispatch
@@ -9198,7 +9199,6 @@ - (NSRect)accessibilityFrame @@ -9207,7 +9208,6 @@ - (NSRect)accessibilityFrame
@end @end
@@ -88,7 +88,7 @@ index 4692a58..93da88a 100644
/* =================================================================== /* ===================================================================
EmacsAccessibilityInteractiveSpan — helpers and implementation EmacsAccessibilityInteractiveSpan — helpers and implementation
=================================================================== */ =================================================================== */
@@ -9528,6 +9528,7 @@ - (void)dealloc @@ -9537,6 +9537,7 @@ - (void)dealloc
[layer release]; [layer release];
#endif #endif
@@ -96,7 +96,7 @@ index 4692a58..93da88a 100644
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -10876,6 +10877,32 @@ - (void)windowDidBecomeKey /* for direct calls */ @@ -10885,6 +10886,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
@@ -129,7 +129,7 @@ index 4692a58..93da88a 100644
} }
@@ -12116,6 +12143,332 @@ - (int) fullscreenState @@ -12125,6 +12152,332 @@ - (int) fullscreenState
return fs_state; return fs_state;
} }

View File

@@ -1,4 +1,4 @@
From 4c53081f3308316fa6d03172d727876b8c90815c Mon Sep 17 00:00:00 2001 From 58616020bbd9e088428e7fd76cc28c19c60cbe05 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

View File

@@ -1,4 +1,4 @@
From a9ea010735a6839a9f5cceb728e1a072cf0b3bfb Mon Sep 17 00:00:00 2001 From 8c424f291b8a635e2b1cd7f3303bc726b5d21a8a Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 14:46:25 +0100 Date: Sat, 28 Feb 2026 14:46:25 +0100
Subject: [PATCH 8/9] ns: announce overlay completion candidates for VoiceOver Subject: [PATCH 8/9] ns: announce overlay completion candidates for VoiceOver
@@ -45,8 +45,8 @@ Key implementation details:
Independent overlay branch, BUF_CHARS_MODIFF gating, candidate Independent overlay branch, BUF_CHARS_MODIFF gating, candidate
--- ---
src/nsterm.h | 1 + src/nsterm.h | 1 +
src/nsterm.m | 306 +++++++++++++++++++++++++++++++++++++++++++++------ src/nsterm.m | 315 ++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 276 insertions(+), 31 deletions(-) 2 files changed, 285 insertions(+), 31 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 6e830de..2102fb9 100644 index 6e830de..2102fb9 100644
@@ -61,10 +61,10 @@ index 6e830de..2102fb9 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 93da88a..b3bdd42 100644 index c35db76..7e34286 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7147,11 +7147,154 @@ Accessibility virtual elements (macOS / Cocoa only) @@ -7156,11 +7156,154 @@ Accessibility virtual elements (macOS / Cocoa only)
/* ---- Helper: extract buffer text for accessibility ---- */ /* ---- Helper: extract buffer text for accessibility ---- */
@@ -220,7 +220,7 @@ index 93da88a..b3bdd42 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)
@@ -7222,7 +7365,7 @@ Accessibility virtual elements (macOS / Cocoa only) @@ -7231,7 +7374,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
@@ -229,7 +229,7 @@ index 93da88a..b3bdd42 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));
@@ -7303,7 +7446,7 @@ Mode lines using icon fonts (e.g. doom-modeline with nerd-font) @@ -7312,7 +7455,7 @@ Mode lines using icon fonts (e.g. doom-modeline with nerd-font)
return NSZeroRect; return NSZeroRect;
/* charpos_start and charpos_len are already in buffer charpos /* charpos_start and charpos_len are already in buffer charpos
@@ -238,7 +238,7 @@ index 93da88a..b3bdd42 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;
@@ -7782,6 +7925,7 @@ @implementation EmacsAccessibilityBuffer @@ -7791,6 +7934,7 @@ @implementation EmacsAccessibilityBuffer
@synthesize cachedOverlayModiff; @synthesize cachedOverlayModiff;
@synthesize cachedTextStart; @synthesize cachedTextStart;
@synthesize cachedModiff; @synthesize cachedModiff;
@@ -246,7 +246,7 @@ index 93da88a..b3bdd42 100644
@synthesize cachedPoint; @synthesize cachedPoint;
@synthesize cachedMarkActive; @synthesize cachedMarkActive;
@synthesize cachedCompletionAnnouncement; @synthesize cachedCompletionAnnouncement;
@@ -7879,7 +8023,7 @@ - (void)ensureTextCache @@ -7888,7 +8032,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
@@ -255,7 +255,7 @@ index 93da88a..b3bdd42 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]);
@@ -7892,16 +8036,15 @@ - (void)ensureTextCache @@ -7901,16 +8045,15 @@ - (void)ensureTextCache
return; return;
ptrdiff_t modiff = BUF_MODIFF (b); ptrdiff_t modiff = BUF_MODIFF (b);
@@ -278,7 +278,7 @@ index 93da88a..b3bdd42 100644
&& cachedTextStart == BUF_BEGV (b) && cachedTextStart == BUF_BEGV (b)
&& pt >= cachedTextStart && pt >= cachedTextStart
&& (textLen == 0 && (textLen == 0
@@ -7918,7 +8061,6 @@ - (void)ensureTextCache @@ -7927,7 +8070,6 @@ - (void)ensureTextCache
[cachedText release]; [cachedText release];
cachedText = [text retain]; cachedText = [text retain];
cachedTextModiff = modiff; cachedTextModiff = modiff;
@@ -286,7 +286,7 @@ index 93da88a..b3bdd42 100644
cachedTextStart = start; cachedTextStart = start;
if (visibleRuns) if (visibleRuns)
@@ -7983,7 +8125,7 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos @@ -7992,7 +8134,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
@@ -295,7 +295,7 @@ index 93da88a..b3bdd42 100644
NSUInteger lo = 0, hi = visibleRunCount; NSUInteger lo = 0, hi = visibleRunCount;
while (lo < hi) while (lo < hi)
{ {
@@ -7996,7 +8138,7 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos @@ -8005,7 +8147,7 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos
else else
{ {
/* Found: charpos is inside this run. Compute UTF-16 delta /* Found: charpos is inside this run. Compute UTF-16 delta
@@ -304,7 +304,7 @@ index 93da88a..b3bdd42 100644
NSUInteger chars_in = (NSUInteger)(charpos - r->charpos); NSUInteger chars_in = (NSUInteger)(charpos - r->charpos);
if (chars_in == 0 || !cachedText) if (chars_in == 0 || !cachedText)
return r->ax_start; return r->ax_start;
@@ -8021,10 +8163,10 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos @@ -8030,10 +8172,10 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos
/* 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
@@ -317,7 +317,7 @@ index 93da88a..b3bdd42 100644
@synchronized (self) @synchronized (self)
{ {
if (visibleRunCount == 0) if (visibleRunCount == 0)
@@ -8058,7 +8200,7 @@ - (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx @@ -8067,7 +8209,7 @@ - (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx
return cp; return cp;
} }
} }
@@ -326,7 +326,7 @@ index 93da88a..b3bdd42 100644
if (lo > 0) if (lo > 0)
{ {
ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1];
@@ -8080,7 +8222,7 @@ - (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx @@ -8089,7 +8231,7 @@ - (ptrdiff_t)charposForAccessibilityIndex:(NSUInteger)ax_idx
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
@@ -335,7 +335,7 @@ index 93da88a..b3bdd42 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
@@ -8434,6 +8576,50 @@ - (NSInteger)accessibilityInsertionPointLineNumber @@ -8443,6 +8585,50 @@ - (NSInteger)accessibilityInsertionPointLineNumber
return [self lineForAXIndex:point_idx]; return [self lineForAXIndex:point_idx];
} }
@@ -386,7 +386,7 @@ index 93da88a..b3bdd42 100644
- (NSRange)accessibilityRangeForLine:(NSInteger)line - (NSRange)accessibilityRangeForLine:(NSInteger)line
{ {
if (![NSThread isMainThread]) if (![NSThread isMainThread])
@@ -8656,7 +8842,7 @@ - (NSRect)accessibilityFrame @@ -8665,7 +8851,7 @@ - (NSRect)accessibilityFrame
/* =================================================================== /* ===================================================================
@@ -395,7 +395,7 @@ index 93da88a..b3bdd42 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).
@@ -8671,7 +8857,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point @@ -8680,7 +8866,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
if (point > self.cachedPoint if (point > self.cachedPoint
&& point - self.cachedPoint == 1) && point - self.cachedPoint == 1)
{ {
@@ -404,7 +404,7 @@ index 93da88a..b3bdd42 100644
[self invalidateTextCache]; [self invalidateTextCache];
[self ensureTextCache]; [self ensureTextCache];
if (cachedText) if (cachedText)
@@ -8690,7 +8876,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point @@ -8699,7 +8885,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
@@ -413,7 +413,7 @@ index 93da88a..b3bdd42 100644
self.cachedPoint = point; self.cachedPoint = point;
NSDictionary *change = @{ NSDictionary *change = @{
@@ -9023,14 +9209,72 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f @@ -9032,14 +9218,81 @@ - (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) --- */
@@ -450,6 +450,14 @@ index 93da88a..b3bdd42 100644
+ { + {
+ self.cachedOverlayModiff = BUF_OVERLAY_MODIFF (b); + self.cachedOverlayModiff = BUF_OVERLAY_MODIFF (b);
+ +
+ /* Overlay completion candidates (Vertico, Icomplete, Ivy) are
+ displayed in the minibuffer. In normal editing buffers,
+ font-lock and other modes change BUF_OVERLAY_MODIFF on
+ every redisplay, triggering O(overlays) work per keystroke.
+ Restrict the scan to minibuffer windows. */
+ if (!MINI_WINDOW_P (w))
+ 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),
@@ -481,6 +489,7 @@ index 93da88a..b3bdd42 100644
+ } + }
+ } + }
} }
+ skip_overlay_scan:;
/* --- 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
@@ -488,7 +497,7 @@ index 93da88a..b3bdd42 100644
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)
{ {
@@ -9200,7 +9444,7 @@ - (NSRect)accessibilityFrame @@ -9209,7 +9462,7 @@ - (NSRect)accessibilityFrame
/* =================================================================== /* ===================================================================
@@ -497,7 +506,7 @@ index 93da88a..b3bdd42 100644
=================================================================== */ =================================================================== */
/* Scan visible range of window W for interactive spans. /* Scan visible range of window W for interactive spans.
@@ -9408,7 +9652,7 @@ - (void) setAccessibilityFocused: (BOOL) focused @@ -9417,7 +9670,7 @@ - (void) setAccessibilityFocused: (BOOL) focused
dispatch_async (dispatch_get_main_queue (), ^{ dispatch_async (dispatch_get_main_queue (), ^{
/* lwin is a Lisp_Object captured by value. This is GC-safe /* lwin is a Lisp_Object captured by value. This is GC-safe
because Lisp_Objects are tagged integers/pointers that because Lisp_Objects are tagged integers/pointers that
@@ -506,7 +515,7 @@ index 93da88a..b3bdd42 100644
Emacs. The WINDOW_LIVE_P check below guards against the Emacs. The WINDOW_LIVE_P check below guards against the
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)))
@@ -9434,7 +9678,7 @@ - (void) setAccessibilityFocused: (BOOL) focused @@ -9443,7 +9696,7 @@ - (void) setAccessibilityFocused: (BOOL) focused
@end @end
@@ -515,7 +524,7 @@ index 93da88a..b3bdd42 100644
Methods are kept here (same .m file) so they access the ivars Methods are kept here (same .m file) so they access the ivars
declared in the @interface ivar block. */ declared in the @interface ivar block. */
@implementation EmacsAccessibilityBuffer (InteractiveSpans) @implementation EmacsAccessibilityBuffer (InteractiveSpans)
@@ -12159,7 +12403,7 @@ - (int) fullscreenState @@ -12168,7 +12421,7 @@ - (int) fullscreenState
if (WINDOW_LEAF_P (w)) if (WINDOW_LEAF_P (w))
{ {
@@ -524,7 +533,7 @@ index 93da88a..b3bdd42 100644
EmacsAccessibilityBuffer *elem EmacsAccessibilityBuffer *elem
= [existing objectForKey:[NSValue valueWithPointer:w]]; = [existing objectForKey:[NSValue valueWithPointer:w]];
if (!elem) if (!elem)
@@ -12193,7 +12437,7 @@ - (int) fullscreenState @@ -12202,7 +12455,7 @@ - (int) fullscreenState
} }
else else
{ {
@@ -533,7 +542,7 @@ index 93da88a..b3bdd42 100644
Lisp_Object child = w->contents; Lisp_Object child = w->contents;
while (!NILP (child)) while (!NILP (child))
{ {
@@ -12305,7 +12549,7 @@ - (void)postAccessibilityUpdates @@ -12314,7 +12567,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
@@ -542,7 +551,7 @@ index 93da88a..b3bdd42 100644
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe); Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
if (!EQ (curRoot, lastRootWindow)) if (!EQ (curRoot, lastRootWindow))
{ {
@@ -12314,12 +12558,12 @@ - (void)postAccessibilityUpdates @@ -12323,12 +12576,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,4 +1,4 @@
From ca488b10323f136b3ce2593b56e72e222bdae7d6 Mon Sep 17 00:00:00 2001 From 2a266c4f8af0277ccd5c7c1997232c2da4eb315c Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 16:01:29 +0100 Date: Sat, 28 Feb 2026 16:01:29 +0100
Subject: [PATCH 9/9] ns: announce child frame completion candidates for Subject: [PATCH 9/9] ns: announce child frame completion candidates for
@@ -109,10 +109,10 @@ index 2102fb9..dd98d56 100644
@end @end
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index b3bdd42..9f37c4e 100644 index 7e34286..c25abfd 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7291,6 +7291,112 @@ visual line index for Zoom (skip whitespace-only lines @@ -7300,6 +7300,112 @@ visual line index for Zoom (skip whitespace-only lines
return nil; return nil;
} }
@@ -225,7 +225,7 @@ index b3bdd42..9f37c4e 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
@@ -9028,6 +9134,7 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b @@ -9037,6 +9143,7 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
ptrdiff_t currentOverlayStart = 0; ptrdiff_t currentOverlayStart = 0;
ptrdiff_t currentOverlayEnd = 0; ptrdiff_t currentOverlayEnd = 0;
@@ -233,7 +233,7 @@ index b3bdd42..9f37c4e 100644
specpdl_ref count2 = SPECPDL_INDEX (); specpdl_ref count2 = SPECPDL_INDEX ();
record_unwind_current_buffer (); record_unwind_current_buffer ();
if (b != current_buffer) if (b != current_buffer)
@@ -9186,6 +9293,7 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b @@ -9195,6 +9302,7 @@ - (void)postCompletionAnnouncementForBuffer:(struct buffer *)b
self.cachedCompletionOverlayEnd = 0; self.cachedCompletionOverlayEnd = 0;
self.cachedCompletionPoint = 0; self.cachedCompletionPoint = 0;
} }
@@ -241,7 +241,7 @@ index b3bdd42..9f37c4e 100644
} }
/* ---- Notification dispatch (main entry point) ---- */ /* ---- Notification dispatch (main entry point) ---- */
@@ -9773,6 +9881,10 @@ - (void)dealloc @@ -9791,6 +9899,10 @@ - (void)dealloc
#endif #endif
[accessibilityElements release]; [accessibilityElements release];
@@ -252,7 +252,7 @@ index b3bdd42..9f37c4e 100644
[[self menu] release]; [[self menu] release];
[super dealloc]; [super dealloc];
} }
@@ -12533,6 +12645,80 @@ - (id)accessibilityFocusedUIElement @@ -12551,6 +12663,80 @@ - (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. */
@@ -333,7 +333,7 @@ index b3bdd42..9f37c4e 100644
- (void)postAccessibilityUpdates - (void)postAccessibilityUpdates
{ {
NSTRACE ("[EmacsView postAccessibilityUpdates]"); NSTRACE ("[EmacsView postAccessibilityUpdates]");
@@ -12543,11 +12729,59 @@ - (void)postAccessibilityUpdates @@ -12561,11 +12747,59 @@ - (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