patches: comprehensive review fixes — B1/W1-4/M1-4
B1: setAccessibilityFocused: on EmacsAccessibilityBuffer now checks
![NSThread isMainThread] and dispatches to main via dispatch_async.
Prevents data race + AppKit thread violation from AX server thread.
W1: accessibilityInsertionPointLineNumber and accessibilityLineForIndex:
now use lineRangeForRange iteration — O(lines) instead of O(chars).
W2: ns_ax_scan_interactive_spans skips non-interactive regions using
Fnext_single_property_change for each scannable property and
Fnext_single_char_property_change for keymap overlays.
W3: ns_ax_event_is_line_nav_key inspects Vthis_command against known
navigation command symbols (next-line, previous-line, evil variants,
dired variants) instead of raw key codes. Tab/backtab fallback
retained via last_command_event.
W4: DEFSYM symbols renamed with ns_ax_ prefix (Qns_ax_button, etc.)
to avoid linker collisions with other Emacs source files.
Lisp symbol strings unchanged.
M3: Removed dead enum values (CheckBox, TextField, PopUpButton) and
corresponding dead switch cases.
M4: Improved accessibilityStyleRangeForIndex: comment documenting the
line-granularity simplification.
README: Updated stats, KNOWN LIMITATIONS, DEFSYM docs, test numbering.
This commit is contained in:
@@ -42,8 +42,9 @@ New functions:
|
||||
ns_ax_frame_for_range: screen rect for a character range via glyph
|
||||
matrix lookup.
|
||||
|
||||
ns_ax_event_is_line_nav_key: detect C-n/C-p/Tab/backtab for
|
||||
forced line-granularity announcements.
|
||||
ns_ax_event_is_line_nav_key: detect line navigation commands
|
||||
via Vthis_command (next-line, previous-line, evil variants)
|
||||
with Tab/backtab fallback via last_command_event.
|
||||
|
||||
ns_ax_scan_interactive_spans: scan visible range for interactive
|
||||
text properties (widget, button, follow-link, org-link,
|
||||
@@ -71,9 +72,10 @@ EmacsView extensions:
|
||||
ns_draw_phys_cursor: stores cursor rect for Zoom, calls
|
||||
UAZoomChangeFocus with correct CG coordinate-space transform.
|
||||
|
||||
DEFSYM additions in syms_of_nsterm: Qwidget, Qbutton, Qfollow_link,
|
||||
Qorg_link, Qcompletion_list_mode, Qcompletion__string, Qcompletion,
|
||||
Qcompletions_highlight, Qbacktab.
|
||||
DEFSYM additions in syms_of_nsterm (ns_ax_ prefix to avoid
|
||||
collisions): Qns_ax_widget, Qns_ax_button, Qns_ax_follow_link,
|
||||
Qns_ax_org_link, Qns_ax_completion_list_mode, Qns_ax_completion__string, Qns_ax_completion,
|
||||
Qns_ax_completions_highlight, Qns_ax_backtab.
|
||||
|
||||
Threading model: all Lisp calls on main thread; AX getters use
|
||||
dispatch_sync to main; index mapping methods are thread-safe (no
|
||||
@@ -86,7 +88,7 @@ Lisp calls, read only immutable NSString and scalar cache).
|
||||
etc/NEWS | 11 +
|
||||
src/nsterm.h | 108 ++
|
||||
src/nsterm.m | 2870 +++++++++++++++++++++++++++++++++++++++++++++++---
|
||||
3 files changed, 2841 insertions(+), 148 deletions(-)
|
||||
3 files changed, 2922 insertions(+), 149 deletions(-)
|
||||
|
||||
diff --git a/etc/NEWS b/etc/NEWS
|
||||
index 7367e3cc..0e4480ad 100644
|
||||
@@ -114,7 +116,7 @@ diff --git a/src/nsterm.h b/src/nsterm.h
|
||||
index 7c1ee4cf..542e7d59 100644
|
||||
--- a/src/nsterm.h
|
||||
+++ b/src/nsterm.h
|
||||
@@ -453,6 +453,100 @@ enum ns_return_frame_mode
|
||||
@@ -453,6 +453,97 @@ enum ns_return_frame_mode
|
||||
@end
|
||||
|
||||
|
||||
@@ -181,11 +183,8 @@ index 7c1ee4cf..542e7d59 100644
|
||||
+{
|
||||
+ EmacsAXSpanTypeButton = 0,
|
||||
+ EmacsAXSpanTypeLink = 1,
|
||||
+ EmacsAXSpanTypeCheckBox = 2,
|
||||
+ EmacsAXSpanTypeTextField = 3,
|
||||
+ EmacsAXSpanTypePopUpButton = 4,
|
||||
+ EmacsAXSpanTypeCompletionItem = 5,
|
||||
+ EmacsAXSpanTypeWidget = 6,
|
||||
+ EmacsAXSpanTypeCompletionItem = 2,
|
||||
+ EmacsAXSpanTypeWidget = 3,
|
||||
+};
|
||||
+
|
||||
+/* A lightweight AX element representing one interactive text span
|
||||
@@ -215,7 +214,7 @@ index 7c1ee4cf..542e7d59 100644
|
||||
/* ==========================================================================
|
||||
|
||||
The main Emacs view
|
||||
@@ -471,6 +565,13 @@ enum ns_return_frame_mode
|
||||
@@ -471,6 +562,13 @@ enum ns_return_frame_mode
|
||||
#ifdef NS_IMPL_COCOA
|
||||
char *old_title;
|
||||
BOOL maximizing_resize;
|
||||
@@ -310,7 +309,7 @@ index 932d209f..ea2de6f2 100644
|
||||
ns_focus (f, NULL, 0);
|
||||
|
||||
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
|
||||
@@ -6849,218 +6891,2414 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
||||
@@ -6849,218 +6891,2471 @@ - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg
|
||||
|
||||
/* ==========================================================================
|
||||
|
||||
@@ -661,7 +660,7 @@ index 932d209f..ea2de6f2 100644
|
||||
-#else
|
||||
- nsfont = (NSFont *) macfont_get_nsctfont (font);
|
||||
-#endif
|
||||
+ Lisp_Object faceSym = Qcompletions_highlight;
|
||||
+ Lisp_Object faceSym = Qns_ax_completions_highlight;
|
||||
+ ptrdiff_t begv = BUF_BEGV (b);
|
||||
+ ptrdiff_t zv = BUF_ZV (b);
|
||||
+ ptrdiff_t best_start = 0;
|
||||
@@ -773,51 +772,59 @@ index 932d209f..ea2de6f2 100644
|
||||
|
||||
- if (font_panel_result)
|
||||
- [font_panel_result autorelease];
|
||||
+/* Detect line-level navigation commands. Inspects Vthis_command
|
||||
+ (the command symbol being executed) rather than raw key codes so
|
||||
+ that remapped bindings (e.g., C-j -> next-line) are recognized.
|
||||
+ Falls back to last_command_event for Tab/backtab which are not
|
||||
+ bound to a single canonical command symbol. */
|
||||
+static bool
|
||||
+ns_ax_event_is_line_nav_key (int *which)
|
||||
+{
|
||||
+ Lisp_Object ev = last_command_event;
|
||||
+ if (CONSP (ev))
|
||||
+ ev = EVENT_HEAD (ev);
|
||||
|
||||
-#ifdef NS_IMPL_COCOA
|
||||
- if (!canceled)
|
||||
- font_panel_result = nil;
|
||||
-#endif
|
||||
+ if (!FIXNUMP (ev))
|
||||
+ {
|
||||
+ /* Handle symbol events: backtab (S-Tab = previous completion). */
|
||||
+ if (SYMBOLP (ev) && !NILP (ev))
|
||||
+ {
|
||||
+ if (EQ (ev, Qbacktab))
|
||||
+ {
|
||||
+ if (which)
|
||||
+ *which = -1;
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
|
||||
- result = font_panel_result;
|
||||
- font_panel_result = nil;
|
||||
+ EMACS_INT c = XFIXNUM (ev);
|
||||
+ if (c == 14) /* C-n */
|
||||
+ /* 1. Check Vthis_command for known navigation command symbols. */
|
||||
+ if (SYMBOLP (Vthis_command) && !NILP (Vthis_command))
|
||||
+ {
|
||||
+ if (which)
|
||||
+ *which = 1;
|
||||
+ Lisp_Object cmd = Vthis_command;
|
||||
+ /* Forward line commands. */
|
||||
+ if (EQ (cmd, intern_c_string ("next-line"))
|
||||
+ || EQ (cmd, intern_c_string ("dired-next-line"))
|
||||
+ || EQ (cmd, intern_c_string ("evil-next-line"))
|
||||
+ || EQ (cmd, intern_c_string ("evil-next-visual-line")))
|
||||
+ {
|
||||
+ if (which) *which = 1;
|
||||
+ return true;
|
||||
+ }
|
||||
+ if (c == 16) /* C-p */
|
||||
+ /* Backward line commands. */
|
||||
+ if (EQ (cmd, intern_c_string ("previous-line"))
|
||||
+ || EQ (cmd, intern_c_string ("dired-previous-line"))
|
||||
+ || EQ (cmd, intern_c_string ("evil-previous-line"))
|
||||
+ || EQ (cmd, intern_c_string ("evil-previous-visual-line")))
|
||||
+ {
|
||||
+ if (which)
|
||||
+ *which = -1;
|
||||
+ if (which) *which = -1;
|
||||
+ return true;
|
||||
+ }
|
||||
+ if (c == 9) /* Tab — next completion/link */
|
||||
+ }
|
||||
+
|
||||
+ /* 2. Fallback: check raw key events for Tab/backtab. */
|
||||
+ Lisp_Object ev = last_command_event;
|
||||
+ if (CONSP (ev))
|
||||
+ ev = EVENT_HEAD (ev);
|
||||
+
|
||||
+ if (SYMBOLP (ev) && EQ (ev, Qns_ax_backtab))
|
||||
+ {
|
||||
+ if (which)
|
||||
+ *which = 1;
|
||||
+ if (which) *which = -1;
|
||||
+ return true;
|
||||
+ }
|
||||
+ if (FIXNUMP (ev) && XFIXNUM (ev) == 9) /* Tab */
|
||||
+ {
|
||||
+ if (which) *which = 1;
|
||||
+ return true;
|
||||
+ }
|
||||
+ return false;
|
||||
@@ -905,7 +912,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ns_ax_get_span_label (ptrdiff_t start, ptrdiff_t end,
|
||||
+ Lisp_Object buf_obj)
|
||||
+{
|
||||
+ Lisp_Object cs = ns_ax_text_prop_at (start, Qcompletion__string,
|
||||
+ Lisp_Object cs = ns_ax_text_prop_at (start, Qns_ax_completion__string,
|
||||
+ buf_obj);
|
||||
+ if (STRINGP (cs))
|
||||
+ return [NSString stringWithLispString: cs];
|
||||
@@ -1001,7 +1008,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ /* Symbols are interned once at startup via DEFSYM in syms_of_nsterm;
|
||||
+ reference them directly here (GC-safe, no repeated obarray lookup). */
|
||||
+
|
||||
+ BOOL is_completion_buf = EQ (BVAR (b, major_mode), Qcompletion_list_mode);
|
||||
+ BOOL is_completion_buf = EQ (BVAR (b, major_mode), Qns_ax_completion_list_mode);
|
||||
+
|
||||
+ NSMutableArray *spans = [NSMutableArray array];
|
||||
+ ptrdiff_t pos = vis_start;
|
||||
@@ -1012,25 +1019,25 @@ index 932d209f..ea2de6f2 100644
|
||||
+ EmacsAXSpanType span_type = (EmacsAXSpanType) -1;
|
||||
+ Lisp_Object limit_prop = Qnil;
|
||||
+
|
||||
+ if (!NILP (Fplist_get (plist, Qwidget, Qnil)))
|
||||
+ if (!NILP (Fplist_get (plist, Qns_ax_widget, Qnil)))
|
||||
+ {
|
||||
+ span_type = EmacsAXSpanTypeWidget;
|
||||
+ limit_prop = Qwidget;
|
||||
+ limit_prop = Qns_ax_widget;
|
||||
+ }
|
||||
+ else if (!NILP (Fplist_get (plist, Qbutton, Qnil)))
|
||||
+ else if (!NILP (Fplist_get (plist, Qns_ax_button, Qnil)))
|
||||
+ {
|
||||
+ span_type = EmacsAXSpanTypeButton;
|
||||
+ limit_prop = Qbutton;
|
||||
+ limit_prop = Qns_ax_button;
|
||||
+ }
|
||||
+ else if (!NILP (Fplist_get (plist, Qfollow_link, Qnil)))
|
||||
+ else if (!NILP (Fplist_get (plist, Qns_ax_follow_link, Qnil)))
|
||||
+ {
|
||||
+ span_type = EmacsAXSpanTypeLink;
|
||||
+ limit_prop = Qfollow_link;
|
||||
+ limit_prop = Qns_ax_follow_link;
|
||||
+ }
|
||||
+ else if (!NILP (Fplist_get (plist, Qorg_link, Qnil)))
|
||||
+ else if (!NILP (Fplist_get (plist, Qns_ax_org_link, Qnil)))
|
||||
+ {
|
||||
+ span_type = EmacsAXSpanTypeLink;
|
||||
+ limit_prop = Qorg_link;
|
||||
+ limit_prop = Qns_ax_org_link;
|
||||
+ }
|
||||
+ else if (is_completion_buf
|
||||
+ && !NILP (Fplist_get (plist, Qmouse_face, Qnil)))
|
||||
@@ -1039,7 +1046,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ don't accidentally merge two column-adjacent candidates
|
||||
+ whose mouse-face regions may share padding whitespace.
|
||||
+ Fall back to mouse-face if completion--string is absent. */
|
||||
+ Lisp_Object cs_sym = Qcompletion__string;
|
||||
+ Lisp_Object cs_sym = Qns_ax_completion__string;
|
||||
+ Lisp_Object cs_val = ns_ax_text_prop_at (pos, cs_sym, buf_obj);
|
||||
+ span_type = EmacsAXSpanTypeCompletionItem;
|
||||
+ limit_prop = NILP (cs_val) ? Qmouse_face : cs_sym;
|
||||
@@ -1063,7 +1070,34 @@ index 932d209f..ea2de6f2 100644
|
||||
+
|
||||
+ if ((NSInteger) span_type == -1)
|
||||
+ {
|
||||
+ pos++;
|
||||
+ /* Skip to the next position where any interactive property
|
||||
+ changes. Try each scannable property in turn and take
|
||||
+ the nearest change point â O(properties) per gap rather
|
||||
+ than O(chars). Fall back to pos+1 as safety net. */
|
||||
+ ptrdiff_t next_interesting = vis_end;
|
||||
+ Lisp_Object skip_props[5]
|
||||
+ = { Qns_ax_widget, Qns_ax_button, Qns_ax_follow_link,
|
||||
+ Qns_ax_org_link, Qmouse_face };
|
||||
+ for (int sp = 0; sp < 5; sp++)
|
||||
+ {
|
||||
+ ptrdiff_t np
|
||||
+ = ns_ax_next_prop_change (pos, skip_props[sp],
|
||||
+ buf_obj, vis_end);
|
||||
+ if (np > pos && np < next_interesting)
|
||||
+ next_interesting = np;
|
||||
+ }
|
||||
+ /* Also check overlay keymap changes. */
|
||||
+ Lisp_Object np_ov
|
||||
+ = Fnext_single_char_property_change (make_fixnum (pos),
|
||||
+ Qkeymap, buf_obj,
|
||||
+ make_fixnum (vis_end));
|
||||
+ if (FIXNUMP (np_ov))
|
||||
+ {
|
||||
+ ptrdiff_t npv = XFIXNUM (np_ov);
|
||||
+ if (npv > pos && npv < next_interesting)
|
||||
+ next_interesting = npv;
|
||||
+ }
|
||||
+ pos = (next_interesting > pos) ? next_interesting : pos + 1;
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
@@ -1102,9 +1136,6 @@ index 932d209f..ea2de6f2 100644
|
||||
+ switch (self.spanType)
|
||||
+ {
|
||||
+ case EmacsAXSpanTypeLink: return NSAccessibilityLinkRole;
|
||||
+ case EmacsAXSpanTypeCheckBox: return NSAccessibilityCheckBoxRole;
|
||||
+ case EmacsAXSpanTypeTextField: return NSAccessibilityTextFieldRole;
|
||||
+ case EmacsAXSpanTypePopUpButton: return NSAccessibilityPopUpButtonRole;
|
||||
+ default: return NSAccessibilityButtonRole;
|
||||
+ }
|
||||
+}
|
||||
@@ -1231,7 +1262,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ {
|
||||
+ ptrdiff_t p = probes[i];
|
||||
+ Lisp_Object cstr = Fget_char_property (make_fixnum (p),
|
||||
+ Qcompletion__string,
|
||||
+ Qns_ax_completion__string,
|
||||
+ Qnil);
|
||||
+ if (STRINGP (cstr))
|
||||
+ text = [NSString stringWithLispString:cstr];
|
||||
@@ -1239,7 +1270,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ {
|
||||
+ /* Fallback: 'completion property used by display-completion-list. */
|
||||
+ cstr = Fget_char_property (make_fixnum (p),
|
||||
+ Qcompletion,
|
||||
+ Qns_ax_completion,
|
||||
+ Qnil);
|
||||
+ if (STRINGP (cstr))
|
||||
+ text = [NSString stringWithLispString:cstr];
|
||||
@@ -1760,6 +1791,16 @@ index 932d209f..ea2de6f2 100644
|
||||
+ if (!flag)
|
||||
+ return;
|
||||
+
|
||||
+ /* VoiceOver may call this from the AX server thread.
|
||||
+ All Lisp reads, block_input, and AppKit calls require main. */
|
||||
+ if (![NSThread isMainThread])
|
||||
+ {
|
||||
+ dispatch_async (dispatch_get_main_queue (), ^{
|
||||
+ [self setAccessibilityFocused:flag];
|
||||
+ });
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ struct window *w = [self validWindow];
|
||||
+ if (!w || !WINDOW_LEAF_P (w))
|
||||
+ return;
|
||||
@@ -1816,12 +1857,19 @@ index 932d209f..ea2de6f2 100644
|
||||
+ if (point_idx > [cachedText length])
|
||||
+ point_idx = [cachedText length];
|
||||
+
|
||||
+ /* Count newlines from start to point_idx. */
|
||||
+ /* Count lines by iterating lineRangeForRange from the start.
|
||||
+ Each call jumps an entire line â O(lines) not O(chars). */
|
||||
+ NSInteger line = 0;
|
||||
+ for (NSUInteger i = 0; i < point_idx; i++)
|
||||
+ NSUInteger scan = 0;
|
||||
+ NSUInteger len = [cachedText length];
|
||||
+ while (scan < point_idx && scan < len)
|
||||
+ {
|
||||
+ if ([cachedText characterAtIndex:i] == '\n')
|
||||
+ NSRange lr = [cachedText lineRangeForRange:NSMakeRange (scan, 0)];
|
||||
+ NSUInteger next = NSMaxRange (lr);
|
||||
+ if (next <= scan) break; /* safety */
|
||||
+ if (next > point_idx) break;
|
||||
+ line++;
|
||||
+ scan = next;
|
||||
+ }
|
||||
+ return line;
|
||||
+}
|
||||
@@ -1866,12 +1914,18 @@ index 932d209f..ea2de6f2 100644
|
||||
+ if (idx > [cachedText length])
|
||||
+ idx = [cachedText length];
|
||||
+
|
||||
+ /* Count newlines from start of cachedText to idx. */
|
||||
+ /* Count lines by iterating lineRangeForRange — O(lines). */
|
||||
+ NSInteger line = 0;
|
||||
+ for (NSUInteger i = 0; i < idx; i++)
|
||||
+ NSUInteger scan = 0;
|
||||
+ NSUInteger len = [cachedText length];
|
||||
+ while (scan < idx && scan < len)
|
||||
+ {
|
||||
+ if ([cachedText characterAtIndex:i] == '\n')
|
||||
+ NSRange lr = [cachedText lineRangeForRange:NSMakeRange (scan, 0)];
|
||||
+ NSUInteger next = NSMaxRange (lr);
|
||||
+ if (next <= scan) break;
|
||||
+ if (next > idx) break;
|
||||
+ line++;
|
||||
+ scan = next;
|
||||
+ }
|
||||
+ return line;
|
||||
+}
|
||||
@@ -1938,7 +1992,9 @@ index 932d209f..ea2de6f2 100644
|
||||
+
|
||||
+- (NSRange)accessibilityStyleRangeForIndex:(NSInteger)index
|
||||
+{
|
||||
+ /* Return the range of the current line — simple approach. */
|
||||
+ /* Return the range of the current line. A more accurate
|
||||
+ implementation would return face/font property boundaries,
|
||||
+ but line granularity is acceptable for VoiceOver. */
|
||||
+ NSInteger line = [self accessibilityLineForIndex:index];
|
||||
+ return [self accessibilityRangeForLine:line];
|
||||
+}
|
||||
@@ -2325,7 +2381,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ /* 1. completion--string at point. */
|
||||
+ Lisp_Object cstr
|
||||
+ = Fget_char_property (make_fixnum (point),
|
||||
+ Qcompletion__string, Qnil);
|
||||
+ Qns_ax_completion__string, Qnil);
|
||||
+ announceText = ns_ax_completion_string_from_prop (cstr);
|
||||
+
|
||||
+ /* 2. Fallback: full line text. */
|
||||
@@ -2393,7 +2449,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ or a list ("candidate" "annotation") for annotated completions.
|
||||
+ In the list case, use car (the completion itself). */
|
||||
+ Lisp_Object cstr = Fget_char_property (make_fixnum (point),
|
||||
+ Qcompletion__string,
|
||||
+ Qns_ax_completion__string,
|
||||
+ Qnil);
|
||||
+ announceText = ns_ax_completion_string_from_prop (cstr);
|
||||
+
|
||||
@@ -2440,7 +2496,7 @@ index 932d209f..ea2de6f2 100644
|
||||
+ /* 3) Fallback: check completions-highlight overlay span at point. */
|
||||
+ if (!announceText)
|
||||
+ {
|
||||
+ Lisp_Object faceSym = Qcompletions_highlight;
|
||||
+ Lisp_Object faceSym = Qns_ax_completions_highlight;
|
||||
+ Lisp_Object overlays = Foverlays_at (make_fixnum (point), Qnil);
|
||||
+ Lisp_Object tail;
|
||||
+ for (tail = overlays; CONSP (tail); tail = XCDR (tail))
|
||||
@@ -2873,7 +2929,7 @@ index 932d209f..ea2de6f2 100644
|
||||
int code;
|
||||
unsigned fnKeysym = 0;
|
||||
static NSMutableArray *nsEvArray;
|
||||
@@ -8237,6 +10475,31 @@ - (void)windowDidBecomeKey /* for direct calls */
|
||||
@@ -8237,6 +10532,31 @@ - (void)windowDidBecomeKey /* for direct calls */
|
||||
XSETFRAME (event.frame_or_window, emacsframe);
|
||||
kbd_buffer_store_event (&event);
|
||||
ns_send_appdefined (-1); // Kick main loop
|
||||
@@ -3238,20 +3294,20 @@ index 932d209f..ea2de6f2 100644
|
||||
@end /* EmacsView */
|
||||
|
||||
|
||||
@@ -11303,6 +13892,18 @@ Convert an X font name (XLFD) to an NS font name.
|
||||
@@ -11303,7 +13892,18 @@ Convert an X font name (XLFD) to an NS font name.
|
||||
DEFSYM (Qns_drag_operation_generic, "ns-drag-operation-generic");
|
||||
DEFSYM (Qns_handle_drag_motion, "ns-handle-drag-motion");
|
||||
|
||||
+ /* Accessibility span scanning symbols. */
|
||||
+ DEFSYM (Qwidget, "widget");
|
||||
+ DEFSYM (Qbutton, "button");
|
||||
+ DEFSYM (Qfollow_link, "follow-link");
|
||||
+ DEFSYM (Qorg_link, "org-link");
|
||||
+ DEFSYM (Qcompletion_list_mode, "completion-list-mode");
|
||||
+ DEFSYM (Qcompletion__string, "completion--string");
|
||||
+ DEFSYM (Qcompletion, "completion");
|
||||
+ DEFSYM (Qcompletions_highlight, "completions-highlight");
|
||||
+ DEFSYM (Qbacktab, "backtab");
|
||||
+ DEFSYM (Qns_ax_widget, "widget");
|
||||
+ DEFSYM (Qns_ax_button, "button");
|
||||
+ DEFSYM (Qns_ax_follow_link, "follow-link");
|
||||
+ DEFSYM (Qns_ax_org_link, "org-link");
|
||||
+ DEFSYM (Qns_ax_completion_list_mode, "completion-list-mode");
|
||||
+ DEFSYM (Qns_ax_completion__string, "completion--string");
|
||||
+ DEFSYM (Qns_ax_completion, "completion");
|
||||
+ DEFSYM (Qns_ax_completions_highlight, "completions-highlight");
|
||||
+ DEFSYM (Qns_ax_backtab, "backtab");
|
||||
+ /* Qmouse_face and Qkeymap are defined in textprop.c / keymap.c. */
|
||||
+
|
||||
Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
|
||||
|
||||
@@ -3,7 +3,7 @@ EMACS NS VOICEOVER ACCESSIBILITY PATCH
|
||||
patch: 0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch
|
||||
author: Martin Sukany <martin@sukany.cz>
|
||||
files: src/nsterm.h (+108 lines)
|
||||
src/nsterm.m (+2588 ins, -140 del, +2448 net)
|
||||
src/nsterm.m (+2638 ins, -140 del, +2498 net)
|
||||
|
||||
|
||||
OVERVIEW
|
||||
@@ -36,8 +36,6 @@ OVERVIEW
|
||||
also covers completion announcements for the *Completions* buffer and
|
||||
Tab-navigable interactive spans for buttons, links, checkboxes,
|
||||
Org-mode links, completion candidates, and keymap overlays.
|
||||
(EmacsAXSpanTypeCheckBox is reserved for future use but not
|
||||
currently scanned.)
|
||||
|
||||
|
||||
ARCHITECTURE
|
||||
@@ -367,10 +365,10 @@ INTERACTIVE SPANS
|
||||
column-adjacent completion candidates from being merged into one span
|
||||
when their mouse-face regions share padding whitespace.
|
||||
|
||||
All property symbols (Qwidget, Qbutton, Qfollow_link, Qorg_link,
|
||||
Qcompletion__string, Qcompletion, Qcompletions_highlight, Qbacktab,
|
||||
Qcompletion_list_mode) are registered with DEFSYM in syms_of_nsterm
|
||||
and referenced directly -- no repeated intern() calls.
|
||||
All property symbols are registered with DEFSYM in syms_of_nsterm
|
||||
using ns_ax_ prefixed C variable names (e.g., Qns_ax_button for
|
||||
"button") to avoid collisions with other Emacs source files.
|
||||
Referenced directly -- no repeated intern() calls.
|
||||
|
||||
Each span is allocated, configured, added to the spans array, then
|
||||
released (the array retains it). The function returns an autoreleased
|
||||
@@ -505,8 +503,8 @@ KNOWN LIMITATIONS
|
||||
covers the common case, but overlay-only changes with a stationary
|
||||
point would be missed. A future fix would compare overlay_modiff.
|
||||
|
||||
- Interactive span scan is O(n) in the visible buffer range. Every
|
||||
character position is visited to find property boundaries. For
|
||||
- Interactive span scan uses property-change jumps to skip
|
||||
non-interactive regions, but still visits every property boundary. For
|
||||
large visible buffers this scan runs on every redisplay cycle
|
||||
whenever interactiveSpansDirty is set. An optimization would use
|
||||
next_single_property_change to skip non-interactive regions in bulk.
|
||||
@@ -516,6 +514,12 @@ KNOWN LIMITATIONS
|
||||
Mode lines with icon fonts (e.g. doom-modeline with nerd-font)
|
||||
produce incomplete or garbled accessibility text.
|
||||
|
||||
- Line counting (accessibilityInsertionPointLineNumber,
|
||||
accessibilityLineForIndex:) uses O(lines) iteration via
|
||||
lineRangeForRange. For buffers with tens of thousands of visible
|
||||
lines this is acceptable but not optimal. A line-number cache
|
||||
keyed on cachedTextModiff could reduce this to O(1).
|
||||
|
||||
- Buffers larger than NS_AX_TEXT_CAP (100,000 UTF-16 units) are
|
||||
truncated. The truncation is silent; AT tools navigating past the
|
||||
truncation boundary may behave unexpectedly.
|
||||
@@ -527,11 +531,11 @@ KNOWN LIMITATIONS
|
||||
has a different accessibility model and requires separate work.
|
||||
|
||||
- Line navigation detection (ns_ax_event_is_line_nav_key) checks
|
||||
raw key codes (C-n = 14, C-p = 16, Tab = 9, backtab symbol).
|
||||
Users who remap keys to navigation commands (e.g. C-j -> next-line)
|
||||
will not get forced line-granularity announcements for those
|
||||
bindings. A future improvement would inspect Vthis_command
|
||||
against known navigation command symbols instead.
|
||||
Vthis_command against known navigation command symbols
|
||||
(next-line, previous-line, evil-next-line, etc.) and falls back
|
||||
to raw key codes for Tab/backtab. Custom navigation commands
|
||||
not in the recognized list will not get forced line-granularity
|
||||
announcements.
|
||||
|
||||
- UAZoomChangeFocus always uses kUAZoomFocusTypeInsertionPoint
|
||||
regardless of cursor style (box, bar, hbar). This is cosmetically
|
||||
@@ -600,9 +604,9 @@ TESTING CHECKLIST
|
||||
C-x o to switch windows. Emacs must not hang.
|
||||
|
||||
Stress test:
|
||||
26. Open a large file (>5000 lines). Navigate with C-v / M-v.
|
||||
25. Open a large file (>5000 lines). Navigate with C-v / M-v.
|
||||
Verify no significant lag in VoiceOver speech response.
|
||||
27. Open an org-mode file with many folded sections. Verify that
|
||||
26. Open an org-mode file with many folded sections. Verify that
|
||||
folded (invisible) text is not announced during navigation.
|
||||
|
||||
-- end of README --
|
||||
|
||||
Reference in New Issue
Block a user