patches: 0008 focus restore after child frame close + corfu delay 2s

- childFrameCompletionActive flag: set by child frame handler,
  cleared when no child frame visible on parent's next cycle
- Posts FocusedUIElementChanged on parent buffer element to
  restore VoiceOver character echo after corfu closes
- corfu-auto-delay: 1.0 → 2.0 (reduce popup noise)
This commit is contained in:
2026-02-28 17:21:44 +01:00
parent afa65a8201
commit 4c7ce352cd
2 changed files with 86 additions and 31 deletions

View File

@@ -630,7 +630,7 @@ Skip for beamer exports — beamer uses adjustbox on plain tabular."
(after! corfu (after! corfu
(setq corfu-auto t (setq corfu-auto t
corfu-auto-delay 1.0 corfu-auto-delay 2.0
corfu-auto-prefix 2 corfu-auto-prefix 2
corfu-cycle t corfu-cycle t
corfu-preselect 'prompt corfu-preselect 'prompt

View File

@@ -1,4 +1,4 @@
From e11d0688f119046827cd1895008d3a93e22f6d0d Mon Sep 17 00:00:00 2001 From 4b4049b4a48b80fb2f786c8777a77bd03a9cf5dc 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
@@ -16,42 +16,49 @@ find the selected candidate. Reuse ns_ax_face_is_selected from
the overlay patch to identify "current", "selected", and the overlay patch to identify "current", "selected", and
"selection" faces. "selection" faces.
Safety measures: Safety:
- Use record_unwind_current_buffer / set_buffer_internal_1 to - record_unwind_current_buffer / set_buffer_internal_1 to switch to
temporarily switch to the child frame buffer before calling the child frame buffer for Fbuffer_substring_no_properties.
Fbuffer_substring_no_properties (which operates on current_buffer). - Re-entrance guard (accessibilityUpdating) before child frame dispatch.
All return paths call unbind_to to restore the original buffer. - BUF_MODIFF gating prevents redundant scans.
- The re-entrance guard (accessibilityUpdating) MUST come before the - WINDOWP, BUFFERP validation for partially initialized frames.
child frame dispatch, because Lisp calls in the scan function can - Buffer size limit (10000 chars) skips non-completion child frames.
trigger redisplay.
- BUF_MODIFF gating prevents redundant scans on every redisplay tick When the child frame closes, post FocusedUIElementChangedNotification
and provides a secondary re-entrance guard. on the parent buffer element to restore VoiceOver's character echo
- Frame state validation (WINDOWP, BUFFERP) handles partially and cursor tracking. The flag childFrameCompletionActive is set by
initialized child frames during creation. the child frame handler and cleared on the parent's next accessibility
- Buffer size limit (10000 chars) skips non-completion child frames cycle when no child frame is visible (via FOR_EACH_FRAME).
such as eldoc documentation or which-key popups.
Announce via AnnouncementRequested to NSApp with High priority. Announce via AnnouncementRequested to NSApp with High priority.
Use direct UAZoomChangeFocus (not the overlayZoomRect flag used Use direct UAZoomChangeFocus because the child frame renders
for minibuffer overlay completion) because the child frame renders independently --- its ns_update_end runs after the parent's
independently --- its ns_update_end runs after the parent frame's
draw_window_cursor, so the last Zoom call wins. draw_window_cursor, so the last Zoom call wins.
* src/nsterm.h (EmacsView): Add announceChildFrameCompletion. * src/nsterm.h (EmacsView): Add announceChildFrameCompletion,
childFrameCompletionActive flag.
* src/nsterm.m (ns_ax_selected_child_frame_text): New function. * src/nsterm.m (ns_ax_selected_child_frame_text): New function.
(EmacsView announceChildFrameCompletion): New method. (EmacsView announceChildFrameCompletion): New method, set parent flag.
(EmacsView postAccessibilityUpdates): Dispatch to child frame (EmacsView postAccessibilityUpdates): Dispatch to child frame handler,
handler for FRAME_PARENT_FRAME frames, under re-entrance guard. refocus parent buffer element when child frame closes.
--- ---
src/nsterm.h | 1 + src/nsterm.h | 2 +
src/nsterm.m | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/nsterm.m | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 205 insertions(+), 1 deletion(-) 2 files changed, 254 insertions(+), 1 deletion(-)
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 5c15639..21b2823 100644 index 5c15639..8b34300 100644
--- a/src/nsterm.h --- a/src/nsterm.h
+++ b/src/nsterm.h +++ b/src/nsterm.h
@@ -657,6 +657,7 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType) @@ -594,6 +594,7 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
NSRect lastAccessibilityCursorRect;
BOOL overlayZoomActive;
NSRect overlayZoomRect;
+ BOOL childFrameCompletionActive;
#endif
BOOL font_panel_active;
NSFont *font_panel_result;
@@ -657,6 +658,7 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
- (void)rebuildAccessibilityTree; - (void)rebuildAccessibilityTree;
- (void)invalidateAccessibilityTree; - (void)invalidateAccessibilityTree;
- (void)postAccessibilityUpdates; - (void)postAccessibilityUpdates;
@@ -60,7 +67,7 @@ index 5c15639..21b2823 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..092bc11 100644 index d13c5c7..03ed77c 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,
@@ -174,7 +181,7 @@ index d13c5c7..092bc11 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,93 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12299,6 +12403,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. */
@@ -240,6 +247,18 @@ index d13c5c7..092bc11 100644
+ NSAccessibilityAnnouncementRequestedNotification, + NSAccessibilityAnnouncementRequestedNotification,
+ annInfo); + annInfo);
+ +
+ /* Mark the parent as having an active child frame completion.
+ When the child frame closes, the parent's next accessibility
+ cycle will post FocusedUIElementChanged to restore VoiceOver's
+ focus to the buffer text element. */
+ struct frame *parent = FRAME_PARENT_FRAME (emacsframe);
+ if (parent)
+ {
+ EmacsView *parentView = FRAME_NS_VIEW (parent);
+ if (parentView)
+ parentView->childFrameCompletionActive = YES;
+ }
+
+ /* Zoom tracking: focus on the selected row in the child frame. + /* Zoom tracking: focus on the selected row in the child frame.
+ Use direct UAZoomChangeFocus rather than overlayZoomRect because + Use direct UAZoomChangeFocus rather than overlayZoomRect because
+ the child frame renders independently of the parent. */ + the child frame renders independently of the parent. */
@@ -268,7 +287,7 @@ index d13c5c7..092bc11 100644
- (void)postAccessibilityUpdates - (void)postAccessibilityUpdates
{ {
NSTRACE ("[EmacsView postAccessibilityUpdates]"); NSTRACE ("[EmacsView postAccessibilityUpdates]");
@@ -12309,11 +12500,23 @@ ns_ax_collect_windows (Lisp_Object window, EmacsView *view, @@ -12309,11 +12512,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
@@ -289,6 +308,42 @@ index d13c5c7..092bc11 100644
+ accessibilityUpdating = NO; + accessibilityUpdating = NO;
+ return; + return;
+ } + }
+
+ /* If a child frame completion was recently active but no child
+ frame is visible anymore, refocus VoiceOver on the buffer
+ element so character echo and cursor tracking resume.
+ Skip if a child frame still exists (completion still open). */
+ if (childFrameCompletionActive)
+ {
+ Lisp_Object tail, frame;
+ BOOL childStillVisible = NO;
+ FOR_EACH_FRAME (tail, frame)
+ if (FRAME_PARENT_FRAME (XFRAME (frame)) == emacsframe
+ && FRAME_VISIBLE_P (XFRAME (frame)))
+ {
+ childStillVisible = YES;
+ break;
+ }
+
+ if (!childStillVisible)
+ {
+ childFrameCompletionActive = NO;
+ EmacsBufferAccessibilityElement *focused = nil;
+ for (id elem in accessibilityElements)
+ if ([elem isKindOfClass:
+ [EmacsBufferAccessibilityElement class]]
+ && [(EmacsBufferAccessibilityElement *)elem
+ isAccessibilityFocused])
+ {
+ focused = elem;
+ break;
+ }
+ if (focused)
+ ns_ax_post_notification (
+ focused,
+ NSAccessibilityFocusedUIElementChangedNotification);
+ }
+ }
+ +
/* 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. */