From 936c251f11c8d12c39b965938eaa5029b33de4a8 Mon Sep 17 00:00:00 2001 From: Daneel Date: Fri, 27 Feb 2026 16:14:47 +0100 Subject: [PATCH] =?UTF-8?q?patches:=20comprehensive=20review=20fixes=20?= =?UTF-8?q?=E2=80=94=20B1/W1-4/M1-4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ...oundsForRange-for-macOS-Zoom-cursor-.patch | 230 +++++++++++------- patches/README.txt | 36 +-- 2 files changed, 163 insertions(+), 103 deletions(-) diff --git a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch index f35a647..8dcf2c4 100644 --- a/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch +++ b/patches/0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch @@ -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; ++ } ++ /* 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; ++ return true; ++ } ++ } ++ ++ /* 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; + return true; + } -+ if (c == 16) /* C-p */ ++ if (FIXNUMP (ev) && XFIXNUM (ev) == 9) /* Tab */ + { -+ if (which) -+ *which = -1; -+ return true; -+ } -+ if (c == 9) /* Tab — next completion/link */ -+ { -+ if (which) -+ *which = 1; ++ 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; + } + @@ -1101,11 +1135,8 @@ 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; ++ case EmacsAXSpanTypeLink: return NSAccessibilityLinkRole; ++ 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') -+ line++; ++ 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') -+ line++; ++ 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)); diff --git a/patches/README.txt b/patches/README.txt index 5f6b4f8..549d1a3 100644 --- a/patches/README.txt +++ b/patches/README.txt @@ -3,7 +3,7 @@ EMACS NS VOICEOVER ACCESSIBILITY PATCH patch: 0001-ns-implement-AXBoundsForRange-for-macOS-Zoom-cursor-.patch author: Martin Sukany 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 --