patches: v5 0007 - review fixes (em dash, comments, safety docs)

This commit is contained in:
2026-02-28 15:36:50 +01:00
parent 99609f0437
commit 9129f032cf

View File

@@ -1,4 +1,4 @@
From 403a1c4664e7491c20eac86c143898bc366a57bc Mon Sep 17 00:00:00 2001 From 3616ffd4289b0a73da93d83fa8dfc7be9d6554b9 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] ns: announce overlay completion candidates for VoiceOver Subject: [PATCH] ns: announce overlay completion candidates for VoiceOver
@@ -32,10 +32,11 @@ Key implementation details:
position, which is the minibuffer input, not the candidate). position, which is the minibuffer input, not the candidate).
- Zoom tracking: store the selected candidate's glyph row rect in - Zoom tracking: store the selected candidate's glyph row rect in
overlayZoomRect. draw_window_cursor checks overlayZoomActive and overlayZoomRect. ns_draw_window_cursor checks overlayZoomActive
uses the stored rect instead of the text cursor rect, keeping and uses the stored rect instead of the text cursor rect, keeping
Zoom focused on the candidate. The flag is cleared when the user Zoom focused on the candidate. The flag is cleared when the user
types (BUF_CHARS_MODIFF changes) to return Zoom to the cursor. types (BUF_CHARS_MODIFF changes) or when no candidate is found
(minibuffer exit, C-g).
* src/nsterm.h (EmacsView): Add overlayZoomActive, overlayZoomRect. * src/nsterm.h (EmacsView): Add overlayZoomActive, overlayZoomRect.
(EmacsAccessibilityBuffer): Add cachedCharsModiff. (EmacsAccessibilityBuffer): Add cachedCharsModiff.
@@ -48,8 +49,8 @@ Independent overlay branch, BUF_CHARS_MODIFF gating, candidate
announcement with overlay Zoom rect storage. announcement with overlay Zoom rect storage.
--- ---
src/nsterm.h | 3 + src/nsterm.h | 3 +
src/nsterm.m | 252 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/nsterm.m | 314 +++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 244 insertions(+), 11 deletions(-) 2 files changed, 281 insertions(+), 36 deletions(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 51c30ca..5c15639 100644 index 51c30ca..5c15639 100644
@@ -73,7 +74,7 @@ index 51c30ca..5c15639 100644
BOOL font_panel_active; BOOL font_panel_active;
NSFont *font_panel_result; NSFont *font_panel_result;
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 1780194..5c3758a 100644 index 1780194..31b2c33 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, @@ -3258,7 +3258,12 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
@@ -90,7 +91,7 @@ index 1780194..5c3758a 100644
NSRect screenRect = [[view window] convertRectToScreen:windowRect]; NSRect screenRect = [[view window] convertRectToScreen:windowRect];
CGRect cgRect = NSRectToCGRect (screenRect); CGRect cgRect = NSRectToCGRect (screenRect);
@@ -6915,11 +6920,145 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) @@ -6915,11 +6920,153 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
are truncated for accessibility purposes. */ are truncated for accessibility purposes. */
#define NS_AX_TEXT_CAP 100000 #define NS_AX_TEXT_CAP 100000
@@ -104,6 +105,9 @@ index 1780194..5c3758a 100644
+ if (SYMBOLP (face) && !NILP (face)) + if (SYMBOLP (face) && !NILP (face))
+ { + {
+ const char *name = SSDATA (SYMBOL_NAME (face)); + const char *name = SSDATA (SYMBOL_NAME (face));
+ /* Substring match is intentionally broad; false positives
+ are harmless since this runs only on overlay strings in
+ the minibuffer during completion. */
+ if (strstr (name, "current") || strstr (name, "selected")) + if (strstr (name, "current") || strstr (name, "selected"))
+ return true; + return true;
+ } + }
@@ -158,9 +162,14 @@ index 1780194..5c3758a 100644
+ if (slen == 0) + if (slen == 0)
+ continue; + continue;
+ +
+ /* Scan for newline positions using SDATA for efficiency. */ + /* Scan for newline positions using SDATA for efficiency.
+ The data pointer is used only in this loop, before any
+ Lisp calls (Fget_text_property etc.) that could trigger
+ GC and relocate string data. */
+ const unsigned char *data = SDATA (str); + const unsigned char *data = SDATA (str);
+ ptrdiff_t byte_len = SBYTES (str); + ptrdiff_t byte_len = SBYTES (str);
+ /* 512 lines is sufficient for any completion UI;
+ vertico-count defaults to 10. */
+ ptrdiff_t line_starts[512]; + ptrdiff_t line_starts[512];
+ ptrdiff_t line_ends[512]; + ptrdiff_t line_ends[512];
+ int nlines = 0; + int nlines = 0;
@@ -237,7 +246,25 @@ index 1780194..5c3758a 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)
@@ -7556,6 +7695,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @@ -6996,7 +7143,7 @@ ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
/* Extract this visible run's text. Use
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. */
Lisp_Object lstr = Fbuffer_substring_no_properties (
make_fixnum (pos), make_fixnum (run_end));
@@ -7077,7 +7224,7 @@ ns_ax_frame_for_range (struct window *w, EmacsView *view,
return NSZeroRect;
/* 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. */
ptrdiff_t cp_start = charpos_start;
ptrdiff_t cp_end = cp_start + charpos_len;
@@ -7556,6 +7703,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
@synthesize cachedOverlayModiff; @synthesize cachedOverlayModiff;
@synthesize cachedTextStart; @synthesize cachedTextStart;
@synthesize cachedModiff; @synthesize cachedModiff;
@@ -245,7 +272,16 @@ index 1780194..5c3758a 100644
@synthesize cachedPoint; @synthesize cachedPoint;
@synthesize cachedMarkActive; @synthesize cachedMarkActive;
@synthesize cachedCompletionAnnouncement; @synthesize cachedCompletionAnnouncement;
@@ -7609,16 +7749,15 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @@ -7596,7 +7744,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
NSTRACE ("EmacsAccessibilityBuffer ensureTextCache");
/* This method is only called from the main thread (AX getters
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
against concurrent reads from AX server thread. */
eassert ([NSThread isMainThread]);
@@ -7609,16 +7757,15 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
return; return;
ptrdiff_t modiff = BUF_MODIFF (b); ptrdiff_t modiff = BUF_MODIFF (b);
@@ -268,7 +304,7 @@ index 1780194..5c3758a 100644
&& cachedTextStart == BUF_BEGV (b) && cachedTextStart == BUF_BEGV (b)
&& pt >= cachedTextStart && pt >= cachedTextStart
&& (textLen == 0 && (textLen == 0
@@ -7635,7 +7774,6 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @@ -7635,7 +7782,6 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
[cachedText release]; [cachedText release];
cachedText = [text retain]; cachedText = [text retain];
cachedTextModiff = modiff; cachedTextModiff = modiff;
@@ -276,7 +312,92 @@ index 1780194..5c3758a 100644
cachedTextStart = start; cachedTextStart = start;
if (visibleRuns) if (visibleRuns)
@@ -8789,10 +8927,102 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @@ -7661,7 +7807,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
/* Binary search: runs are sorted by charpos (ascending). Find the
run whose [charpos, charpos+length) range contains the target,
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;
while (lo < hi)
{
@@ -7674,7 +7820,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
else
{
/* Found: charpos is inside this run. Compute UTF-16 delta
- directly from cachedText — no Lisp calls needed. */
+ directly from cachedText --- no Lisp calls needed. */
NSUInteger chars_in = (NSUInteger)(charpos - r->charpos);
if (chars_in == 0 || !cachedText)
return r->ax_start;
@@ -7699,10 +7845,10 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
/* Convert accessibility string index to buffer charpos.
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
{
- /* May be called from AX server thread — synchronize. */
+ /* May be called from AX server thread --- synchronize. */
@synchronized (self)
{
if (visibleRunCount == 0)
@@ -7736,7 +7882,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
return cp;
}
}
- /* Past end — return last charpos. */
+ /* Past end --- return last charpos. */
if (lo > 0)
{
ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1];
@@ -7758,7 +7904,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
deadlocking the AX server thread. This is prevented by:
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
concurrent Lisp code can modify state between checks.
3. block_input prevents timer events and process output from
@@ -8166,7 +8312,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
if (idx > [cachedText length])
idx = [cachedText length];
- /* Count lines by iterating lineRangeForRange — O(lines). */
+ /* Count lines by iterating lineRangeForRange --- O(lines). */
NSInteger line = 0;
NSUInteger scan = 0;
NSUInteger len = [cachedText length];
@@ -8422,7 +8568,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
/* ===================================================================
- EmacsAccessibilityBuffer (Notifications) — AX event dispatch
+ EmacsAccessibilityBuffer (Notifications) --- AX event dispatch
These methods notify VoiceOver of text and selection changes.
Called from the redisplay cycle (postAccessibilityUpdates).
@@ -8437,7 +8583,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
if (point > self.cachedPoint
&& point - self.cachedPoint == 1)
{
- /* Single char inserted — refresh cache and grab it. */
+ /* Single char inserted --- refresh cache and grab it. */
[self invalidateTextCache];
[self ensureTextCache];
if (cachedText)
@@ -8456,7 +8602,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
/* 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
- same user action — they are mutually exclusive. */
+ same user action --- they are mutually exclusive. */
self.cachedPoint = point;
NSDictionary *change = @{
@@ -8789,14 +8935,110 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
BOOL markActive = !NILP (BVAR (b, mark_active)); BOOL markActive = !NILP (BVAR (b, mark_active));
/* --- Text changed (edit) --- */ /* --- Text changed (edit) --- */
@@ -375,11 +496,114 @@ index 1780194..5c3758a 100644
+ } + }
+ else + else
+ { + {
+ /* No selected candidate --- overlay completion ended
+ (minibuffer exit, C-g, etc.) or overlay has no
+ recognizable selection face. Return Zoom to the
+ text cursor. */
+ self.emacsView->overlayZoomActive = NO; + self.emacsView->overlayZoomActive = NO;
+ } + }
} }
/* --- 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. */
else if (point != self.cachedPoint || markActive != self.cachedMarkActive)
{
@@ -8966,7 +9208,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
/* ===================================================================
- EmacsAccessibilityInteractiveSpan — helpers and implementation
+ EmacsAccessibilityInteractiveSpan --- helpers and implementation
=================================================================== */
/* Scan visible range of window W for interactive spans.
@@ -9157,7 +9399,7 @@ ns_ax_scan_interactive_spans (struct window *w,
- (BOOL) isAccessibilityFocused
{
/* Read the cached point stored by EmacsAccessibilityBuffer on the main
- thread — safe to read from any thread (plain ptrdiff_t, no Lisp calls). */
+ thread --- safe to read from any thread (plain ptrdiff_t, no Lisp calls). */
EmacsAccessibilityBuffer *pb = self.parentBuffer;
if (!pb)
return NO;
@@ -9174,7 +9416,7 @@ ns_ax_scan_interactive_spans (struct window *w,
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
- remain valid across GC — GC does not relocate objects in
+ remain valid across GC --- GC does not relocate objects in
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)))
@@ -9200,7 +9442,7 @@ ns_ax_scan_interactive_spans (struct window *w,
@end
-/* EmacsAccessibilityBuffer — InteractiveSpans category.
+/* EmacsAccessibilityBuffer --- InteractiveSpans category.
Methods are kept here (same .m file) so they access the ivars
declared in the @interface ivar block. */
@implementation EmacsAccessibilityBuffer (InteractiveSpans)
@@ -10520,13 +10762,13 @@ ns_in_echo_area (void)
if (old_title == 0)
{
char *t = strdup ([[[self window] title] UTF8String]);
- char *pos = strstr (t, " — ");
+ char *pos = strstr (t, " --- ");
if (pos)
*pos = '\0';
old_title = t;
}
size_title = xmalloc (strlen (old_title) + 40);
- esprintf (size_title, "%s — (%d × %d)", old_title, cols, rows);
+ esprintf (size_title, "%s --- (%d × %d)", old_title, cols, rows);
[window setTitle: [NSString stringWithUTF8String: size_title]];
[window display];
xfree (size_title);
@@ -11922,7 +12164,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
if (WINDOW_LEAF_P (w))
{
- /* Buffer element — reuse existing if available. */
+ /* Buffer element --- reuse existing if available. */
EmacsAccessibilityBuffer *elem
= [existing objectForKey:[NSValue valueWithPointer:w]];
if (!elem)
@@ -11956,7 +12198,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
}
else
{
- /* Internal (combination) window — recurse into children. */
+ /* Internal (combination) window --- recurse into children. */
Lisp_Object child = w->contents;
while (!NILP (child))
{
@@ -12068,7 +12310,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
accessibilityUpdating = YES;
/* 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);
if (!EQ (curRoot, lastRootWindow))
{
@@ -12077,12 +12319,12 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
}
/* 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. */
if (!accessibilityTreeValid)
{
[self rebuildAccessibilityTree];
- /* Invalidate span cache — window layout changed. */
+ /* Invalidate span cache --- window layout changed. */
for (EmacsAccessibilityElement *elem in accessibilityElements)
if ([elem isKindOfClass: [EmacsAccessibilityBuffer class]])
[(EmacsAccessibilityBuffer *) elem invalidateInteractiveSpans];
-- --
2.43.0 2.43.0