patches: 0007 fix hl-line-mode blocking SelectedTextChanged

hl-line-mode (and similar) bumps BUF_MODIFF via text property
changes on every cursor movement. The else-if structure caused
the modiff branch to fire (skipping ValueChanged correctly) but
also blocked the cursor-move branch (SelectedTextChanged).

Fix: use textDidChange flag to decouple the two branches.
ValueChanged and SelectedTextChanged remain mutually exclusive
for real edits, but SelectedTextChanged now fires when only
text properties changed.
This commit is contained in:
2026-02-28 17:38:05 +01:00
parent 83c3c09858
commit edbed0a116
2 changed files with 40 additions and 24 deletions

View File

@@ -1,7 +1,7 @@
From 8157451dedda9b43de47f82d1deb85c9d2853a35 Mon Sep 17 00:00:00 2001 From 7d5fe56a0e86aee931787eda2e4988a6b4815291 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 1/2] ns: announce overlay completion candidates for VoiceOver
Completion frameworks such as Vertico, Ivy, and Icomplete render Completion frameworks such as Vertico, Ivy, and Icomplete render
candidates via overlay before-string/after-string properties rather candidates via overlay before-string/after-string properties rather
@@ -52,8 +52,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 | 319 +++++++++++++++++++++++++++++++++++++++++++++------ src/nsterm.m | 335 +++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 286 insertions(+), 36 deletions(-) 2 files changed, 300 insertions(+), 38 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
@@ -77,7 +77,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..d13c5c7 100644 index 1780194..6efeb1d 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,
@@ -403,26 +403,34 @@ index 1780194..d13c5c7 100644
self.cachedPoint = point; self.cachedPoint = point;
NSDictionary *change = @{ NSDictionary *change = @{
@@ -8789,14 +8938,112 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, @@ -8789,16 +8938,126 @@ 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) --- */
+ ptrdiff_t chars_modiff = BUF_CHARS_MODIFF (b); + ptrdiff_t chars_modiff = BUF_CHARS_MODIFF (b);
+ BOOL textDidChange = NO;
if (modiff != self.cachedModiff) if (modiff != self.cachedModiff)
{ {
self.cachedModiff = modiff; self.cachedModiff = modiff;
- [self postTextChangedNotification:point]; - [self postTextChangedNotification:point];
+ /* Only post ValueChanged when actual characters changed. + /* Only post ValueChanged when actual characters changed.
+ Text property changes (e.g. face updates from + Text property changes (e.g. face updates from hl-line-mode,
+ vertico--prompt-selection) bump BUF_MODIFF but not + vertico--prompt-selection) bump BUF_MODIFF but not
+ BUF_CHARS_MODIFF. Posting ValueChanged for property-only + BUF_CHARS_MODIFF. Posting ValueChanged for property-only
+ changes causes VoiceOver to say "new line" when the diff + changes causes VoiceOver to say "new line" when the diff
+ is non-empty due to overlay content changes. */ + is non-empty due to overlay content changes.
+
+ Use textDidChange to avoid blocking the cursor-move branch
+ below: property-only changes must not prevent
+ SelectedTextChanged from firing when point also moved
+ (e.g. hl-line-mode updates face properties on every cursor
+ movement in dired and other read-only buffers). */
+ if (chars_modiff != self.cachedCharsModiff) + if (chars_modiff != self.cachedCharsModiff)
+ { + {
+ self.cachedCharsModiff = chars_modiff; + self.cachedCharsModiff = chars_modiff;
+ self.emacsView->overlayZoomActive = NO; + self.emacsView->overlayZoomActive = NO;
+ [self postTextChangedNotification:point]; + [self postTextChangedNotification:point];
+ textDidChange = YES;
+ } + }
+ } + }
+ +
@@ -514,11 +522,19 @@ index 1780194..d13c5c7 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
+ 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) + Skip when ValueChanged was already posted (edits and selection
+ moves are mutually exclusive per the WebKit/Chromium pattern).
+ But DO fire when only text properties changed (BUF_MODIFF bumped
+ without BUF_CHARS_MODIFF) --- hl-line-mode and similar packages
+ update face properties on every cursor movement. */
+ if (!textDidChange
+ && (point != self.cachedPoint || markActive != self.cachedMarkActive))
{ {
@@ -8966,7 +9213,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem, ptrdiff_t oldPoint = self.cachedPoint;
BOOL oldMarkActive = self.cachedMarkActive;
@@ -8966,7 +9225,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
/* =================================================================== /* ===================================================================
@@ -527,7 +543,7 @@ index 1780194..d13c5c7 100644
=================================================================== */ =================================================================== */
/* Scan visible range of window W for interactive spans. /* Scan visible range of window W for interactive spans.
@@ -9157,7 +9404,7 @@ ns_ax_scan_interactive_spans (struct window *w, @@ -9157,7 +9416,7 @@ ns_ax_scan_interactive_spans (struct window *w,
- (BOOL) isAccessibilityFocused - (BOOL) isAccessibilityFocused
{ {
/* Read the cached point stored by EmacsAccessibilityBuffer on the main /* Read the cached point stored by EmacsAccessibilityBuffer on the main
@@ -536,7 +552,7 @@ index 1780194..d13c5c7 100644
EmacsAccessibilityBuffer *pb = self.parentBuffer; EmacsAccessibilityBuffer *pb = self.parentBuffer;
if (!pb) if (!pb)
return NO; return NO;
@@ -9174,7 +9421,7 @@ ns_ax_scan_interactive_spans (struct window *w, @@ -9174,7 +9433,7 @@ ns_ax_scan_interactive_spans (struct window *w,
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
@@ -545,7 +561,7 @@ index 1780194..d13c5c7 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)))
@@ -9200,7 +9447,7 @@ ns_ax_scan_interactive_spans (struct window *w, @@ -9200,7 +9459,7 @@ ns_ax_scan_interactive_spans (struct window *w,
@end @end
@@ -554,7 +570,7 @@ index 1780194..d13c5c7 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)
@@ -10520,13 +10767,13 @@ ns_in_echo_area (void) @@ -10520,13 +10779,13 @@ ns_in_echo_area (void)
if (old_title == 0) if (old_title == 0)
{ {
char *t = strdup ([[[self window] title] UTF8String]); char *t = strdup ([[[self window] title] UTF8String]);
@@ -570,7 +586,7 @@ index 1780194..d13c5c7 100644
[window setTitle: [NSString stringWithUTF8String: size_title]]; [window setTitle: [NSString stringWithUTF8String: size_title]];
[window display]; [window display];
xfree (size_title); xfree (size_title);
@@ -11922,7 +12169,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -11922,7 +12181,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
if (WINDOW_LEAF_P (w)) if (WINDOW_LEAF_P (w))
{ {
@@ -579,7 +595,7 @@ index 1780194..d13c5c7 100644
EmacsAccessibilityBuffer *elem EmacsAccessibilityBuffer *elem
= [existing objectForKey:[NSValue valueWithPointer:w]]; = [existing objectForKey:[NSValue valueWithPointer:w]];
if (!elem) if (!elem)
@@ -11956,7 +12203,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -11956,7 +12215,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
} }
else else
{ {
@@ -588,7 +604,7 @@ index 1780194..d13c5c7 100644
Lisp_Object child = w->contents; Lisp_Object child = w->contents;
while (!NILP (child)) while (!NILP (child))
{ {
@@ -12068,7 +12315,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12068,7 +12327,7 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
accessibilityUpdating = YES; accessibilityUpdating = YES;
/* Detect window tree change (split, delete, new buffer). Compare /* Detect window tree change (split, delete, new buffer). Compare
@@ -597,7 +613,7 @@ index 1780194..d13c5c7 100644
Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe); Lisp_Object curRoot = FRAME_ROOT_WINDOW (emacsframe);
if (!EQ (curRoot, lastRootWindow)) if (!EQ (curRoot, lastRootWindow))
{ {
@@ -12077,12 +12324,12 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12077,12 +12336,12 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
} }
/* 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 1a5ab75243e83665e6c1df9191bb5471d5233e5c Mon Sep 17 00:00:00 2001 From 0d018f15c088668df039e4776972969da1a719d5 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 2/2] ns: announce child frame completion candidates for Subject: [PATCH 2/2] ns: announce child frame completion candidates for
@@ -67,7 +67,7 @@ index 5c15639..8b34300 100644
@end @end
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index d13c5c7..2e7d776 100644 index 6efeb1d..0255239 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -7066,6 +7066,110 @@ ns_ax_selected_overlay_text (struct buffer *b, @@ -7066,6 +7066,110 @@ ns_ax_selected_overlay_text (struct buffer *b,
@@ -181,7 +181,7 @@ index d13c5c7..2e7d776 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
@@ -12299,6 +12403,105 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12311,6 +12415,105 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
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. */
@@ -287,7 +287,7 @@ index d13c5c7..2e7d776 100644
- (void)postAccessibilityUpdates - (void)postAccessibilityUpdates
{ {
NSTRACE ("[EmacsView postAccessibilityUpdates]"); NSTRACE ("[EmacsView postAccessibilityUpdates]");
@@ -12309,11 +12512,59 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12321,11 +12524,59 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view,
/* 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