patches: fix Tab navigation in completion buffer (probe order + Tab detection)
This commit is contained in:
@@ -1,13 +1,13 @@
|
|||||||
From 1caa0476b3109ad583715c2f8a90c943780ffcb9 Mon Sep 17 00:00:00 2001
|
From 03ad0337a0f4cf8b01261eb34068fb17cc925e96 Mon Sep 17 00:00:00 2001
|
||||||
From: Martin Sukany <martin@sukany.cz>
|
From: Martin Sukany <martin@sukany.cz>
|
||||||
Date: Fri, 27 Feb 2026 09:57:00 +0100
|
Date: Fri, 27 Feb 2026 10:10:36 +0100
|
||||||
Subject: [PATCH] ns: implement VoiceOver accessibility (AXBoundsForRange, line
|
Subject: [PATCH] ns: implement VoiceOver accessibility (AXBoundsForRange, line
|
||||||
nav, completions)
|
nav, completions)
|
||||||
|
|
||||||
---
|
---
|
||||||
src/nsterm.h | 73 ++
|
src/nsterm.h | 73 ++
|
||||||
src/nsterm.m | 2249 +++++++++++++++++++++++++++++++++++++++++++++++---
|
src/nsterm.m | 2262 +++++++++++++++++++++++++++++++++++++++++++++++---
|
||||||
2 files changed, 2187 insertions(+), 135 deletions(-)
|
2 files changed, 2210 insertions(+), 125 deletions(-)
|
||||||
|
|
||||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||||
index 7c1ee4c..717a838 100644
|
index 7c1ee4c..717a838 100644
|
||||||
@@ -108,7 +108,7 @@ index 7c1ee4c..717a838 100644
|
|||||||
|
|
||||||
|
|
||||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||||
index 932d209..220dccf 100644
|
index 932d209..0fa6b2d 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)
|
||||||
@@ -169,7 +169,7 @@ index 932d209..220dccf 100644
|
|||||||
ns_focus (f, NULL, 0);
|
ns_focus (f, NULL, 0);
|
||||||
|
|
||||||
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
||||||
@@ -6849,207 +6886,1829 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
@@ -6849,194 +6886,1849 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
|
|
||||||
@@ -508,20 +508,23 @@ index 932d209..220dccf 100644
|
|||||||
+ ptrdiff_t best_end = 0;
|
+ ptrdiff_t best_end = 0;
|
||||||
+ ptrdiff_t best_dist = PTRDIFF_MAX;
|
+ ptrdiff_t best_dist = PTRDIFF_MAX;
|
||||||
+ BOOL found = NO;
|
+ BOOL found = NO;
|
||||||
|
+
|
||||||
- if (!font_panel_result && FRAME_FONT (emacsframe))
|
+ /* Fast path: look at point and immediate neighbors first.
|
||||||
+ /* Fast path: look at point and immediate neighbors first. */
|
+ Prefer point+1 over point-1: when Tab moves to a new completion,
|
||||||
+ ptrdiff_t probes[3] = { point, point - 1, point + 1 };
|
+ point is at the START of the new entry while point-1 is still
|
||||||
|
+ inside the previous entry's overlay. Forward probe finds the
|
||||||
|
+ correct new entry; backward probe finds the wrong old one. */
|
||||||
|
+ ptrdiff_t probes[3] = { point, point + 1, point - 1 };
|
||||||
+ for (int i = 0; i < 3 && !found; i++)
|
+ for (int i = 0; i < 3 && !found; i++)
|
||||||
{
|
+ {
|
||||||
- font_panel_result
|
|
||||||
- = macfont_get_nsctfont (FRAME_FONT (emacsframe));
|
|
||||||
+ ptrdiff_t p = probes[i];
|
+ ptrdiff_t p = probes[i];
|
||||||
+ if (p < begv || p > zv)
|
+ if (p < begv || p > zv)
|
||||||
+ continue;
|
+ continue;
|
||||||
|
|
||||||
- if (font_panel_result)
|
- if (!font_panel_result && FRAME_FONT (emacsframe))
|
||||||
- [font_panel_result retain];
|
- {
|
||||||
|
- font_panel_result
|
||||||
|
- = macfont_get_nsctfont (FRAME_FONT (emacsframe));
|
||||||
+ Lisp_Object overlays = Foverlays_at (make_fixnum (p), Qnil);
|
+ Lisp_Object overlays = Foverlays_at (make_fixnum (p), Qnil);
|
||||||
+ Lisp_Object tail;
|
+ Lisp_Object tail;
|
||||||
+ for (tail = overlays; CONSP (tail); tail = XCDR (tail))
|
+ for (tail = overlays; CONSP (tail); tail = XCDR (tail))
|
||||||
@@ -543,9 +546,10 @@ index 932d209..220dccf 100644
|
|||||||
+ found = YES;
|
+ found = YES;
|
||||||
+ break;
|
+ break;
|
||||||
+ }
|
+ }
|
||||||
}
|
+ }
|
||||||
|
|
||||||
- [NSApp stop: self];
|
- if (font_panel_result)
|
||||||
|
- [font_panel_result retain];
|
||||||
+ if (!found)
|
+ if (!found)
|
||||||
+ {
|
+ {
|
||||||
+ for (ptrdiff_t scan = begv; scan < zv; scan++)
|
+ for (ptrdiff_t scan = begv; scan < zv; scan++)
|
||||||
@@ -582,8 +586,9 @@ index 932d209..220dccf 100644
|
|||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
}
|
||||||
+
|
|
||||||
|
- [NSApp stop: self];
|
||||||
+ if (!found)
|
+ if (!found)
|
||||||
+ return NO;
|
+ return NO;
|
||||||
+
|
+
|
||||||
@@ -605,7 +610,19 @@ index 932d209..220dccf 100644
|
|||||||
- [font_panel_result release];
|
- [font_panel_result release];
|
||||||
- font_panel_result = nil;
|
- font_panel_result = nil;
|
||||||
+ if (!FIXNUMP (ev))
|
+ if (!FIXNUMP (ev))
|
||||||
+ return false;
|
+ {
|
||||||
|
+ /* Handle symbol events: backtab (S-Tab = previous completion). */
|
||||||
|
+ if (SYMBOLP (ev) && !NILP (ev))
|
||||||
|
+ {
|
||||||
|
+ if (EQ (ev, intern ("backtab")))
|
||||||
|
+ {
|
||||||
|
+ if (which)
|
||||||
|
+ *which = -1;
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
|
||||||
- [NSApp stop: self];
|
- [NSApp stop: self];
|
||||||
+ EMACS_INT c = XFIXNUM (ev);
|
+ EMACS_INT c = XFIXNUM (ev);
|
||||||
@@ -621,6 +638,12 @@ index 932d209..220dccf 100644
|
|||||||
+ *which = -1;
|
+ *which = -1;
|
||||||
+ return true;
|
+ return true;
|
||||||
+ }
|
+ }
|
||||||
|
+ if (c == 9) /* Tab — next completion/link */
|
||||||
|
+ {
|
||||||
|
+ if (which)
|
||||||
|
+ *which = 1;
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
+ return false;
|
+ return false;
|
||||||
}
|
}
|
||||||
-#endif
|
-#endif
|
||||||
@@ -674,10 +697,10 @@ index 932d209..220dccf 100644
|
|||||||
+ struct buffer *oldb = current_buffer;
|
+ struct buffer *oldb = current_buffer;
|
||||||
+ if (b != current_buffer)
|
+ if (b != current_buffer)
|
||||||
+ set_buffer_internal_1 (b);
|
+ set_buffer_internal_1 (b);
|
||||||
|
+
|
||||||
- font_panel_active = YES;
|
+ /* Prefer canonical completion candidate string from text property.
|
||||||
- timeout = make_timespec (0, 100000000);
|
+ Try both completion--string (new API, set by minibuffer.el) and
|
||||||
+ /* Prefer canonical completion candidate string from text property. */
|
+ completion (older API used by some modes). */
|
||||||
+ ptrdiff_t probes[2] = { start, end - 1 };
|
+ ptrdiff_t probes[2] = { start, end - 1 };
|
||||||
+ for (int i = 0; i < 2 && !text; i++)
|
+ for (int i = 0; i < 2 && !text; i++)
|
||||||
+ {
|
+ {
|
||||||
@@ -687,6 +710,25 @@ index 932d209..220dccf 100644
|
|||||||
+ Qnil);
|
+ Qnil);
|
||||||
+ if (STRINGP (cstr))
|
+ if (STRINGP (cstr))
|
||||||
+ text = [NSString stringWithLispString:cstr];
|
+ text = [NSString stringWithLispString:cstr];
|
||||||
|
+ if (!text)
|
||||||
|
+ {
|
||||||
|
+ /* Fallback: 'completion property used by display-completion-list. */
|
||||||
|
+ cstr = Fget_char_property (make_fixnum (p),
|
||||||
|
+ intern ("completion"),
|
||||||
|
+ Qnil);
|
||||||
|
+ if (STRINGP (cstr))
|
||||||
|
+ text = [NSString stringWithLispString:cstr];
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- font_panel_active = YES;
|
||||||
|
- timeout = make_timespec (0, 100000000);
|
||||||
|
+ if (!text)
|
||||||
|
+ {
|
||||||
|
+ NSUInteger ax_s = [elem accessibilityIndexForCharpos:start];
|
||||||
|
+ NSUInteger ax_e = [elem accessibilityIndexForCharpos:end];
|
||||||
|
+ if (ax_e > ax_s && ax_e <= [cachedText length])
|
||||||
|
+ text = [cachedText substringWithRange:NSMakeRange (ax_s, ax_e - ax_s)];
|
||||||
+ }
|
+ }
|
||||||
|
|
||||||
- block_input ();
|
- block_input ();
|
||||||
@@ -699,23 +741,11 @@ index 932d209..220dccf 100644
|
|||||||
- )
|
- )
|
||||||
- ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES);
|
- ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES);
|
||||||
- unblock_input ();
|
- unblock_input ();
|
||||||
+ if (!text)
|
|
||||||
+ {
|
|
||||||
+ NSUInteger ax_s = [elem accessibilityIndexForCharpos:start];
|
|
||||||
+ NSUInteger ax_e = [elem accessibilityIndexForCharpos:end];
|
|
||||||
+ if (ax_e > ax_s && ax_e <= [cachedText length])
|
|
||||||
+ text = [cachedText substringWithRange:NSMakeRange (ax_s, ax_e - ax_s)];
|
|
||||||
+ }
|
|
||||||
|
|
||||||
- if (font_panel_result)
|
|
||||||
- [font_panel_result autorelease];
|
|
||||||
+ if (b != oldb)
|
+ if (b != oldb)
|
||||||
+ set_buffer_internal_1 (oldb);
|
+ set_buffer_internal_1 (oldb);
|
||||||
|
|
||||||
-#ifdef NS_IMPL_COCOA
|
- if (font_panel_result)
|
||||||
- if (!canceled)
|
- [font_panel_result autorelease];
|
||||||
- font_panel_result = nil;
|
|
||||||
-#endif
|
|
||||||
+ if (text)
|
+ if (text)
|
||||||
+ {
|
+ {
|
||||||
+ text = [text stringByTrimmingCharactersInSet:
|
+ text = [text stringByTrimmingCharactersInSet:
|
||||||
@@ -724,48 +754,45 @@ index 932d209..220dccf 100644
|
|||||||
+ text = nil;
|
+ text = nil;
|
||||||
+ }
|
+ }
|
||||||
|
|
||||||
- result = font_panel_result;
|
-#ifdef NS_IMPL_COCOA
|
||||||
- font_panel_result = nil;
|
- if (!canceled)
|
||||||
|
- font_panel_result = nil;
|
||||||
|
-#endif
|
||||||
+ return text;
|
+ return text;
|
||||||
+}
|
+}
|
||||||
|
|
||||||
|
- result = font_panel_result;
|
||||||
|
- font_panel_result = nil;
|
||||||
|
|
||||||
- [[fm fontPanel: YES] setIsVisible: NO];
|
- [[fm fontPanel: YES] setIsVisible: NO];
|
||||||
- font_panel_active = NO;
|
- font_panel_active = NO;
|
||||||
|
+@implementation EmacsAccessibilityElement
|
||||||
|
|
||||||
- if (result)
|
- if (result)
|
||||||
- return ns_font_desc_to_font_spec ([result fontDescriptor],
|
- return ns_font_desc_to_font_spec ([result fontDescriptor],
|
||||||
- result);
|
- result);
|
||||||
+@implementation EmacsAccessibilityElement
|
|
||||||
|
|
||||||
- return Qnil;
|
|
||||||
+- (instancetype)init
|
+- (instancetype)init
|
||||||
+{
|
+{
|
||||||
+ self = [super init];
|
+ self = [super init];
|
||||||
+ if (self)
|
+ if (self)
|
||||||
+ self.lispWindow = Qnil;
|
+ self.lispWindow = Qnil;
|
||||||
+ return self;
|
+ return self;
|
||||||
}
|
+}
|
||||||
|
|
||||||
-- (BOOL)acceptsFirstResponder
|
- return Qnil;
|
||||||
+/* Return the associated Emacs window if it is still live, else NULL.
|
+/* Return the associated Emacs window if it is still live, else NULL.
|
||||||
+ Use this instead of storing a raw struct window * which can become a
|
+ Use this instead of storing a raw struct window * which can become a
|
||||||
+ dangling pointer after delete-window or kill-buffer. */
|
+ dangling pointer after delete-window or kill-buffer. */
|
||||||
+- (struct window *)validWindow
|
+- (struct window *)validWindow
|
||||||
{
|
+{
|
||||||
- NSTRACE ("[EmacsView acceptsFirstResponder]");
|
|
||||||
- return YES;
|
|
||||||
+ if (NILP (self.lispWindow) || !WINDOW_LIVE_P (self.lispWindow))
|
+ if (NILP (self.lispWindow) || !WINDOW_LIVE_P (self.lispWindow))
|
||||||
+ return NULL;
|
+ return NULL;
|
||||||
+ return XWINDOW (self.lispWindow);
|
+ return XWINDOW (self.lispWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
-/* Tell NS we want to accept clicks that activate the window */
|
-- (BOOL)acceptsFirstResponder
|
||||||
-- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
|
|
||||||
+- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)ew height:(int)eh
|
+- (NSRect)screenRectFromEmacsX:(int)x y:(int)y width:(int)ew height:(int)eh
|
||||||
{
|
+{
|
||||||
- NSTRACE_MSG ("First mouse event: type=%ld, clickCount=%ld",
|
|
||||||
- [theEvent type], [theEvent clickCount]);
|
|
||||||
- return ns_click_through;
|
|
||||||
+ EmacsView *view = self.emacsView;
|
+ EmacsView *view = self.emacsView;
|
||||||
+ if (!view || ![view window])
|
+ if (!view || ![view window])
|
||||||
+ return NSZeroRect;
|
+ return NSZeroRect;
|
||||||
@@ -773,36 +800,32 @@ index 932d209..220dccf 100644
|
|||||||
+ NSRect r = NSMakeRect (x, y, ew, eh);
|
+ NSRect r = NSMakeRect (x, y, ew, eh);
|
||||||
+ NSRect winRect = [view convertRect:r toView:nil];
|
+ NSRect winRect = [view convertRect:r toView:nil];
|
||||||
+ return [[view window] convertRectToScreen:winRect];
|
+ return [[view window] convertRectToScreen:winRect];
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+- (BOOL)isAccessibilityElement
|
||||||
|
{
|
||||||
|
- NSTRACE ("[EmacsView acceptsFirstResponder]");
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
-/* Tell NS we want to accept clicks that activate the window */
|
||||||
|
-- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
|
||||||
|
+/* ---- Hierarchy plumbing (required for VoiceOver to find us) ---- */
|
||||||
|
+
|
||||||
|
+- (id)accessibilityParent
|
||||||
|
{
|
||||||
|
- NSTRACE_MSG ("First mouse event: type=%ld, clickCount=%ld",
|
||||||
|
- [theEvent type], [theEvent clickCount]);
|
||||||
|
- return ns_click_through;
|
||||||
|
+ return NSAccessibilityUnignoredAncestor (self.emacsView);
|
||||||
}
|
}
|
||||||
-- (void)resetCursorRects
|
-- (void)resetCursorRects
|
||||||
+
|
+
|
||||||
+- (BOOL)isAccessibilityElement
|
+- (id)accessibilityWindow
|
||||||
{
|
{
|
||||||
- NSRect visible = [self visibleRect];
|
- NSRect visible = [self visibleRect];
|
||||||
- NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe);
|
- NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe);
|
||||||
- NSTRACE ("[EmacsView resetCursorRects]");
|
- NSTRACE ("[EmacsView resetCursorRects]");
|
||||||
+ return YES;
|
|
||||||
+}
|
|
||||||
|
|
||||||
- if (currentCursor == nil)
|
|
||||||
- currentCursor = [NSCursor arrowCursor];
|
|
||||||
+/* ---- Hierarchy plumbing (required for VoiceOver to find us) ---- */
|
|
||||||
|
|
||||||
- if (!NSIsEmptyRect (visible))
|
|
||||||
- [self addCursorRect: visible cursor: currentCursor];
|
|
||||||
+- (id)accessibilityParent
|
|
||||||
+{
|
|
||||||
+ return NSAccessibilityUnignoredAncestor (self.emacsView);
|
|
||||||
+}
|
|
||||||
|
|
||||||
-#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101300
|
|
||||||
-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
|
||||||
- if ([currentCursor respondsToSelector: @selector(setOnMouseEntered:)])
|
|
||||||
-#endif
|
|
||||||
- [currentCursor setOnMouseEntered: YES];
|
|
||||||
-#endif
|
|
||||||
+- (id)accessibilityWindow
|
|
||||||
+{
|
|
||||||
+ return [self.emacsView window];
|
+ return [self.emacsView window];
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
@@ -2118,23 +2141,10 @@ index 932d209..220dccf 100644
|
|||||||
+ NSRect visible = [self visibleRect];
|
+ NSRect visible = [self visibleRect];
|
||||||
+ NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe);
|
+ NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe);
|
||||||
+ NSTRACE ("[EmacsView resetCursorRects]");
|
+ NSTRACE ("[EmacsView resetCursorRects]");
|
||||||
+
|
|
||||||
+ if (currentCursor == nil)
|
|
||||||
+ currentCursor = [NSCursor arrowCursor];
|
|
||||||
+
|
|
||||||
+ if (!NSIsEmptyRect (visible))
|
|
||||||
+ [self addCursorRect: visible cursor: currentCursor];
|
|
||||||
+
|
|
||||||
+#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 101300
|
|
||||||
+#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
|
||||||
+ if ([currentCursor respondsToSelector: @selector(setOnMouseEntered:)])
|
|
||||||
+#endif
|
|
||||||
+ [currentCursor setOnMouseEntered: YES];
|
|
||||||
+#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (currentCursor == nil)
|
||||||
@@ -8237,6 +9896,28 @@ - (void)windowDidBecomeKey /* for direct calls */
|
currentCursor = [NSCursor arrowCursor];
|
||||||
|
@@ -8237,6 +9929,28 @@ - (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
|
||||||
@@ -2163,7 +2173,7 @@ index 932d209..220dccf 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9474,6 +11155,304 @@ - (int) fullscreenState
|
@@ -9474,6 +11188,304 @@ - (int) fullscreenState
|
||||||
return fs_state;
|
return fs_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user