patches: fix O(position) lag — O(1) fast path in accessibilityIndexForCharpos:
accessibilityIndexForCharpos: walked composed character sequences from run.ax_start up to the target charpos offset. For a run covering an entire ASCII buffer, chars_in = pt - BUF_BEGV, making each call O(cursor_position). This method is called from ensureTextCache on EVERY redisplay frame (as part of the cache validity check), making each frame O(position) even when the buffer is completely unchanged. At line 34,000 of a large file this is ~1,000,000 iterations per frame. Fix: when ax_length == length for a run (all single-unit characters), the ax_index is simply ax_start + chars_in. O(1) instead of O(N). This is the symmetric counterpart to the charposForAccessibilityIndex: fast path added in the previous commit. Both conversion directions now run in O(1) for pure-ASCII buffers.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
From 8dd1a4cd6d3f58a3c6f9454ba1690a442c2048fe Mon Sep 17 00:00:00 2001
|
||||
From a8172542efe800cf6d29759007c9f826630da881 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Sukany <martin@sukany.cz>
|
||||
Date: Sat, 28 Feb 2026 14:46:25 +0100
|
||||
Subject: [PATCH 7/8] ns: announce overlay completion candidates for VoiceOver
|
||||
@@ -45,8 +45,8 @@ Key implementation details:
|
||||
Independent overlay branch, BUF_CHARS_MODIFF gating, candidate
|
||||
---
|
||||
src/nsterm.h | 1 +
|
||||
src/nsterm.m | 332 ++++++++++++++++++++++++++++++++++++++++++++-------
|
||||
2 files changed, 290 insertions(+), 43 deletions(-)
|
||||
src/nsterm.m | 352 ++++++++++++++++++++++++++++++++++++++++++++-------
|
||||
2 files changed, 306 insertions(+), 47 deletions(-)
|
||||
|
||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||
index 6e830de..2102fb9 100644
|
||||
@@ -61,7 +61,7 @@ index 6e830de..2102fb9 100644
|
||||
@property (nonatomic, assign) BOOL cachedMarkActive;
|
||||
@property (nonatomic, copy) NSString *cachedCompletionAnnouncement;
|
||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||
index 0a70f3e..c74eaf1 100644
|
||||
index 51813b5..7ce683d 100644
|
||||
--- a/src/nsterm.m
|
||||
+++ b/src/nsterm.m
|
||||
@@ -7254,11 +7254,154 @@ Accessibility virtual elements (macOS / Cocoa only)
|
||||
@@ -307,16 +307,43 @@ index 0a70f3e..c74eaf1 100644
|
||||
NSUInteger lo = 0, hi = visibleRunCount;
|
||||
while (lo < hi)
|
||||
{
|
||||
@@ -8110,7 +8245,7 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos
|
||||
@@ -8109,10 +8244,6 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos
|
||||
lo = mid + 1;
|
||||
else
|
||||
{
|
||||
/* Found: charpos is inside this run. Compute UTF-16 delta
|
||||
-<<<<<<< Updated upstream
|
||||
- /* Found: charpos is inside this run. Compute UTF-16 delta
|
||||
- directly from cachedText — no Lisp calls needed. */
|
||||
-=======
|
||||
/* Found: charpos is inside this run. Compute UTF-16 delta.
|
||||
Fast path for pure-ASCII runs (ax_length == length): every
|
||||
Emacs charpos maps to exactly one UTF-16 code unit, so the
|
||||
@@ -8122,7 +8253,23 @@ conversion is O(1). This matters because ensureTextCache
|
||||
cost per frame even when the buffer is unchanged.
|
||||
Multi-byte runs fall through to the sequence walk, bounded
|
||||
by run length (visible window), not total buffer size. */
|
||||
->>>>>>> Stashed changes
|
||||
+ NSUInteger chars_in = (NSUInteger)(charpos - r->charpos);
|
||||
+ if (chars_in == 0)
|
||||
+ return r->ax_start;
|
||||
+ if (r->ax_length == (NSUInteger) r->length)
|
||||
+ return r->ax_start + chars_in;
|
||||
+ if (!cachedText)
|
||||
+ return r->ax_start;
|
||||
+ NSUInteger run_end_ax = r->ax_start + r->ax_length;
|
||||
+ NSUInteger scan = r->ax_start;
|
||||
+ for (NSUInteger c = 0; c < chars_in && scan < run_end_ax; c++)
|
||||
+ {
|
||||
+ NSRange seq = [cachedText
|
||||
+ rangeOfComposedCharacterSequenceAtIndex:scan];
|
||||
+ scan = NSMaxRange (seq);
|
||||
+ }=======
|
||||
+ directly from cachedText --- no Lisp calls needed. */
|
||||
+>>>>>>> 8dd1a4c (ns: announce overlay completion candidates for VoiceOver)
|
||||
NSUInteger chars_in = (NSUInteger)(charpos - r->charpos);
|
||||
if (chars_in == 0 || !cachedText)
|
||||
if (chars_in == 0)
|
||||
return r->ax_start;
|
||||
@@ -8135,10 +8270,10 @@ - (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos
|
||||
@@ -8151,10 +8298,10 @@ by run length (visible window), not total buffer size. */
|
||||
|
||||
/* Convert accessibility string index to buffer charpos.
|
||||
Safe to call from any thread: uses only cachedText (NSString) and
|
||||
@@ -329,7 +356,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
@synchronized (self)
|
||||
{
|
||||
if (visibleRunCount == 0)
|
||||
@@ -8180,7 +8315,7 @@ the slow path (composed character sequence walk), which is
|
||||
@@ -8196,7 +8343,7 @@ the slow path (composed character sequence walk), which is
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
@@ -338,7 +365,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
if (lo > 0)
|
||||
{
|
||||
ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1];
|
||||
@@ -8202,7 +8337,7 @@ the slow path (composed character sequence walk), which is
|
||||
@@ -8218,7 +8365,7 @@ the slow path (composed character sequence walk), which is
|
||||
deadlocking the AX server thread. This is prevented by:
|
||||
|
||||
1. validWindow checks WINDOW_LIVE_P and BUFFERP before every
|
||||
@@ -347,7 +374,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
2. All dispatch_sync blocks run on the main thread where no
|
||||
concurrent Lisp code can modify state between checks.
|
||||
3. block_input prevents timer events and process output from
|
||||
@@ -8556,6 +8691,50 @@ - (NSInteger)accessibilityInsertionPointLineNumber
|
||||
@@ -8572,6 +8719,50 @@ - (NSInteger)accessibilityInsertionPointLineNumber
|
||||
return [self lineForAXIndex:point_idx];
|
||||
}
|
||||
|
||||
@@ -398,7 +425,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
- (NSRange)accessibilityRangeForLine:(NSInteger)line
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
@@ -8778,7 +8957,7 @@ - (NSRect)accessibilityFrame
|
||||
@@ -8794,7 +8985,7 @@ - (NSRect)accessibilityFrame
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
@@ -407,7 +434,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
|
||||
These methods notify VoiceOver of text and selection changes.
|
||||
Called from the redisplay cycle (postAccessibilityUpdates).
|
||||
@@ -8793,7 +8972,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
|
||||
@@ -8809,7 +9000,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
|
||||
if (point > self.cachedPoint
|
||||
&& point - self.cachedPoint == 1)
|
||||
{
|
||||
@@ -416,7 +443,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
[self invalidateTextCache];
|
||||
[self ensureTextCache];
|
||||
if (cachedText)
|
||||
@@ -8812,7 +8991,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
|
||||
@@ -8828,7 +9019,7 @@ - (void)postTextChangedNotification:(ptrdiff_t)point
|
||||
/* Update cachedPoint here so the selection-move branch does NOT
|
||||
fire for point changes caused by edits. WebKit and Chromium
|
||||
never send both ValueChanged and SelectedTextChanged for the
|
||||
@@ -425,7 +452,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
self.cachedPoint = point;
|
||||
|
||||
NSDictionary *change = @{
|
||||
@@ -9145,16 +9324,83 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
|
||||
@@ -9161,16 +9352,83 @@ - (void)postAccessibilityNotificationsForFrame:(struct frame *)f
|
||||
BOOL markActive = !NILP (BVAR (b, mark_active));
|
||||
|
||||
/* --- Text changed (edit) --- */
|
||||
@@ -513,7 +540,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
{
|
||||
ptrdiff_t oldPoint = self.cachedPoint;
|
||||
BOOL oldMarkActive = self.cachedMarkActive;
|
||||
@@ -9322,7 +9568,7 @@ - (NSRect)accessibilityFrame
|
||||
@@ -9338,7 +9596,7 @@ - (NSRect)accessibilityFrame
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
@@ -522,7 +549,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
=================================================================== */
|
||||
|
||||
/* Scan visible range of window W for interactive spans.
|
||||
@@ -9530,7 +9776,7 @@ - (void) setAccessibilityFocused: (BOOL) focused
|
||||
@@ -9546,7 +9804,7 @@ - (void) setAccessibilityFocused: (BOOL) focused
|
||||
dispatch_async (dispatch_get_main_queue (), ^{
|
||||
/* lwin is a Lisp_Object captured by value. This is GC-safe
|
||||
because Lisp_Objects are tagged integers/pointers that
|
||||
@@ -531,7 +558,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
Emacs. The WINDOW_LIVE_P check below guards against the
|
||||
window being deleted between capture and execution. */
|
||||
if (!WINDOWP (lwin) || NILP (Fwindow_live_p (lwin)))
|
||||
@@ -9556,7 +9802,7 @@ - (void) setAccessibilityFocused: (BOOL) focused
|
||||
@@ -9572,7 +9830,7 @@ - (void) setAccessibilityFocused: (BOOL) focused
|
||||
|
||||
@end
|
||||
|
||||
@@ -540,7 +567,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
Methods are kept here (same .m file) so they access the ivars
|
||||
declared in the @interface ivar block. */
|
||||
@implementation EmacsAccessibilityBuffer (InteractiveSpans)
|
||||
@@ -12278,7 +12524,7 @@ - (int) fullscreenState
|
||||
@@ -12294,7 +12552,7 @@ - (int) fullscreenState
|
||||
|
||||
if (WINDOW_LEAF_P (w))
|
||||
{
|
||||
@@ -549,7 +576,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
EmacsAccessibilityBuffer *elem
|
||||
= [existing objectForKey:[NSValue valueWithPointer:w]];
|
||||
if (!elem)
|
||||
@@ -12312,7 +12558,7 @@ - (int) fullscreenState
|
||||
@@ -12328,7 +12586,7 @@ - (int) fullscreenState
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -558,7 +585,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
Lisp_Object child = w->contents;
|
||||
while (!NILP (child))
|
||||
{
|
||||
@@ -12424,7 +12670,7 @@ - (void)postAccessibilityUpdates
|
||||
@@ -12440,7 +12698,7 @@ - (void)postAccessibilityUpdates
|
||||
accessibilityUpdating = YES;
|
||||
|
||||
/* Detect window tree change (split, delete, new buffer). Compare
|
||||
@@ -567,7 +594,7 @@ index 0a70f3e..c74eaf1 100644
|
||||
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
|
||||
if (!EQ (curRoot, lastRootWindow))
|
||||
{
|
||||
@@ -12433,12 +12679,12 @@ - (void)postAccessibilityUpdates
|
||||
@@ -12449,12 +12707,12 @@ - (void)postAccessibilityUpdates
|
||||
}
|
||||
|
||||
/* If tree is stale, rebuild FIRST so we don't iterate freed
|
||||
|
||||
Reference in New Issue
Block a user