Commit Graph

203 Commits

Author SHA1 Message Date
8196205c3d Revert "ax: fix VoiceOver cursor sync and word double-read"
This reverts commit bc5714b7b7.
2026-03-02 14:28:09 +01:00
bc5714b7b7 ax: fix VoiceOver cursor sync and word double-read
Bug 1 (VO cursor not following Emacs cursor):
- Remove FocusedUIElementChangedNotification on emacsView (was a no-op:
  VO re-queried the same element)
- For Emacs-initiated char/word moves, keep natural next/previous
  direction instead of forcing discontiguous; SelectedTextChanged with
  direction=next advances VO browse cursor sequentially
- Only force discontiguous for line-boundary crossings and large jumps

Bug 2 (word double-read with punctuation):
- Root cause was FocusedUIElementChanged causing VO re-anchor speech
  on top of the explicit word announcement
- Removing FocusedUIElementChanged eliminates the duplicate speech
- Add emacsInitiated parameter to postFocusedCursorNotification;
  omit AXTextSelectionGranularity for Emacs-initiated moves so VO
  does not auto-speak (only explicit announcements provide speech)
- isWordMove now triggers on emacsInitiated flag (Emacs-initiated
  word moves always get explicit announcement)
2026-03-02 12:38:40 +01:00
53ea58725e Fix patch 0002: restore closing ')' after evil-line EQ removal
Removing the evil-mode EQ conditions left the if() without its
closing paren.  Fix:

  if (EQ (cmd, Qns_ax_next_line)
      || EQ (cmd, Qns_ax_dired_next_line))   <- add ')'

Same for previous_line.  Verified: all 9 patches apply and build.
2026-03-02 12:07:11 +01:00
bbdd0143e3 Fix patch 0008: update '-' line to match patch 0003 without 'evil'
Commit 7609922 changed patch 0003 comment from
'for evil block-cursor mode' to 'for block-cursor mode'.
Patch 0008 had a '-' (removal) line still referencing 'for evil
block-cursor mode', which git am could not find in the file ->
'patch does not apply'.

Verified locally: git am now applies all 9 patches cleanly on f0dbe25.
2026-03-02 12:04:29 +01:00
9700d0643d Fix patch 0008: shift nsterm.m hunk line numbers by +4
Adding 4 lines in patch 0003 (punctuation trim fix) shifted all
subsequent nsterm.m positions by 4.  Update the @@ -NNNN offsets
for all 14 nsterm.m hunks at line >= 9060 in patch 0008.
2026-03-02 11:37:09 +01:00
6ea5ae8a90 Fix patch 0008 corrupt hunk header: -4400,16 -> -4400,15
Old-count in macos.texi hunk was off by one; git am reported
'corrupt patch at line 83'.
2026-03-02 11:31:14 +01:00
91981bf77e Fix VO cursor sync, word double-read, and VO->Emacs positioning
patch 0002: Do not activate mark in setAccessibilitySelectedTextRange.
VoiceOver range.length is an internal word-boundary hint, not a text
selection.  Activating the mark made accessibilitySelectedTextRange
return a non-zero length, causing VoiceOver to position its browse
cursor at the END of the selection instead of the START.

patch 0003: Fix word announcement double-read and punctuation.
- Explicit word announcement only for Emacs-initiated (discontiguous)
  moves; VO-initiated (sequential) word navigation relies on VO
  auto-speech from the granularity=word notification, preventing
  double-read.
- Strip trailing/leading punctuation from word announcements using
  punctuationCharacterSet so 'Ahoj,' is announced as 'Ahoj'.

patch 0008: Post FocusedUIElementChangedNotification on the EmacsView
(not just on line-granularity moves) for all Emacs-initiated cursor
movements.  Posting on the view causes VoiceOver to re-query
accessibilityFocusedUIElement and re-anchor its browse cursor at the
current accessibilitySelectedTextRange.  Also fixes a pre-existing
off-by-one in the macos.texi hunk header.
2026-03-02 11:12:48 +01:00
760992224c Remove evil-mode and doom-emacs references from patches
These patches target GNU Emacs core; third-party package references
are unacceptable for upstream submission.

patch 0006: doom-modeline -> icon-based mode-lines; Evil-mode -> generic
patch 0007: fix hunk context hint (doom-modeline -> nerd-font icons)
patch 0008: Evil-mode block cursors -> Block-style cursors

The evil DEFSYM (Qns_ax_evil_next_line etc.) were already removed
by cd288e8.
2026-03-02 11:05:57 +01:00
cd288e8c76 Fix three VoiceOver bugs: crash, cursor sync, word announcement
Bug 1 (crash): postEchoAreaAnnouncementIfNeeded called Fbuffer_string()
without block_input, allowing timer events to interleave with Lisp
calls and corrupt buffer state.  Added block_input/unblock_input via
record_unwind_protect_void.  Also removed unpaired unblock_input()
in postCompletionAnnouncementForBuffer (patch 0003) that became a
double-unblock after patch 0008 added block_input + unwind protect.

Bug 2 (cursor sync): VoiceOver browse cursor did not follow Emacs
keyboard cursor because SelectedTextChanged with discontiguous
direction alone is not sufficient for VoiceOver to re-anchor.  Added
NSAccessibilityFocusedUIElementChangedNotification post when Emacs
moves the cursor across a line boundary, forcing VoiceOver to re-query
accessibilitySelectedTextRange.

Bug 3 (word off-by-one): Evil w (next-word) read the previous word
instead of the destination word.  VO auto-speech from
SelectedTextChanged with direction=next+granularity=word reads the
traversed word, not the arrived-at word.  Added explicit word
announcement (like char moves) that reads the word AT the new cursor
position using whitespace-delimited word boundary scan.
2026-03-02 10:42:54 +01:00
7ab55a7fb3 Fix crash in announceChildFrameCompletion: BUFFER_LIVE_P before BUF_MODIFF
BUF_MODIFF(b) dereferences the struct buffer pointer unconditionally.
If the buffer was killed, this accesses freed memory and crashes.
Check BUFFER_LIVE_P first.

Use precise Python line-index swap instead of Edit tool to avoid
accidentally replacing other patch content.
2026-03-02 10:22:36 +01:00
9f5e5b6e83 Revert patch 0003 granularity change -- patch 0008 already has this fix
Patch 0008 modifies postFocusedCursorNotification to add
'&& direction != discontiguous' to the isCharMove guard, which IS
the cursor sync fix.  Changing patch 0003 independently broke
patch 0008's context match.  Restore patch 0003 to original.
2026-03-02 10:17:53 +01:00
164a194da5 Fix patch 0003 hunk header count and non-ASCII character
The edit adding 2 comment lines broke the +8758,552 count (now 554).
Also replace UTF-8 arrow -> ASCII to avoid git am corruption.
2026-03-02 10:13:49 +01:00
083b4a4acd Fix crash and cursor sync in accessibility patches
Patch 0008: Move BUFFER_LIVE_P check before BUF_MODIFF dereference
in announceChildFrameCompletion.  Accessing BUF_MODIFF on a killed
buffer is a null/garbage dereference.

Patch 0003: Always include AXTextSelectionGranularity in
postFocusedCursorNotification, including for character-granularity
moves.  Without granularity, VoiceOver leaves its browse cursor at
the previous position on C-f/C-b/arrow moves.  The explicit
AnnouncementRequested (High priority) still overrides VO speech
for evil block-cursor correctness.
2026-03-02 10:10:36 +01:00
a1d028f334 patches: remove stale 0010 (content already in 0000) 2026-03-01 21:21:42 +01:00
93ea536942 patches: fix VO browse cursor sync — Emacs→VoiceOver direction 2026-03-01 21:18:34 +01:00
57086b88ef patches: full maintainer review pass — all blocking issues fixed 2026-03-01 21:10:35 +01:00
1dcc7f8352 patches: fix minibuffer — typing priority, echo area, C-g 2026-03-01 21:05:10 +01:00
371d90bd4f patches: review fixes — comments, echo area, doc updates 2026-03-01 20:59:37 +01:00
46930281db patches: fix echo area — read echo_area_buffer[0] directly 2026-03-01 20:51:02 +01:00
6d2d702aa7 patches: fix encoding + cursor sync + echo area 2026-03-01 15:02:20 +01:00
b7d0188cbb patches: fix VoiceOver cursor sync + echo area (base f8d9ecb restored) 2026-03-01 14:51:58 +01:00
7ddebc2579 patches: rebase onto upstream a755d7f (2026-03-01) 2026-03-01 14:44:39 +01:00
3502cfaf25 patches: fix VoiceOver rotor cursor sync + echo area announcements 2026-03-01 14:41:40 +01:00
bcb25088dd patches: announce echo area messages to VoiceOver 2026-03-01 14:25:20 +01:00
0d4d874607 patches: fix VoiceOver rotor cursor sync on large jumps (LayoutChanged) 2026-03-01 14:20:48 +01:00
2402c50117 patches: fix fold/unfold comment — outline-mode uses overlays in Emacs 28+ 2026-03-01 13:44:21 +01:00
6703914305 patches: fix fold/unfold AX cache regression (BUF_OVERLAY_MODIFF) 2026-03-01 13:43:25 +01:00
f108952af1 patches: fix remaining line-length violations and doc string corruption
- Correct three pre-existing DEFVAR_BOOL doc strings (ns-use-native-
  fullscreen, ns-use-mwheel-acceleration, ns-use-mwheel-momentum) that
  were accidentally modified by an earlier rebase; restore original text
- Break @property line in nsterm.h to 79 chars
- Replace em-dash with --- and break 80-char comment line in patch 0004
2026-03-01 10:05:49 +01:00
71c81abcae patches: address all maintainer review issues
- Issue 1: Add explicit ApplicationServices import for UAZoomEnabled/
  UAZoomChangeFocus (was implicit via Carbon.h, now explicit)
- Issue 2: Rename FOR_EACH_FRAME variable 'frames' -> 'frame' (plural
  was misleading; matches Emacs convention)
- Issue 3: Move unblock_input before ObjC calls in
  postCompletionAnnouncementForBuffer: to avoid holding block_input
  during @synchronized operations
- Issue 4: Fix DEFVAR_BOOL doc and Texinfo: initial value is nil,
  not t; auto-detection sets it at startup
- Issue 5: Replace magic 10000 with NS_AX_MAX_COMPLETION_BUFFER_CHARS
  constant with explanatory comment
- Issue 6: Add comment to lineStartOffsets loop explaining it is gated
  on BUF_CHARS_MODIFF and never runs on the hot path
- Issue 8: Rewrite all 9 commit messages to GNU ChangeLog format with
  '* file (symbol): description' entries
- Issue 9: Break 81-char @interface line in nsterm.h
- Issue 10: Add WINDOWP/BUFFERP guards before dereferencing
  cf->selected_window and cw->contents in ns_zoom_find_child_frame_candidate
- Issue 11: Fix @pxref -> @xref at sentence start in macos.texi
2026-03-01 09:44:47 +01:00
e0343db56c patches: fix leftover conflict markers in patch 0002 2026-03-01 09:17:45 +01:00
0c13f5d6a3 patches: fix O(position) lag — O(1) fast path in accessibilityIndexForCharpos:
accessibilityIndexForCharpos: walked composed character sequences
from run.ax_start up to the target charpos offset.  For a run
covering an entire ASCII buffer, chars_in = pt - BUF_BEGV, making
each call O(cursor_position).

This method is called from ensureTextCache on EVERY redisplay frame
(as part of the cache validity check), making each frame O(position)
even when the buffer is completely unchanged.  At line 34,000 of a
large file this is ~1,000,000 iterations per frame.

Fix: when ax_length == length for a run (all single-unit characters),
the ax_index is simply ax_start + chars_in.  O(1) instead of O(N).

This is the symmetric counterpart to the charposForAccessibilityIndex:
fast path added in the previous commit.  Both conversion directions
now run in O(1) for pure-ASCII buffers.
2026-03-01 09:14:52 +01:00
fb68dd50ea patches: fix O(position) lag — O(1) fast path in charposForAccessibilityIndex:
charposForAccessibilityIndex: walked composed character sequences
from the start of a visible run to the target AX index.  For a run
covering an entire ASCII buffer, this is O(cursor_position): moving
to line 10,000 requires ~500,000 iterations per call.

The method is called on every SelectedTextChanged notification
response (accessibilityBoundsForRange: from the AX server for cursor
tracking), making cursor movement O(position) in large files.

Fix: when ax_length == length for a run (all characters are single
AX index units — true for all ASCII/Latin text), the charpos offset
is simply ax_idx - run.ax_start.  O(1) instead of O(position).

Multi-byte runs (emoji, CJK, non-BMP) fall back to the sequence walk,
bounded by run length (visible window size), not total buffer size.
2026-03-01 09:03:01 +01:00
31ad038360 patches: fix O(position) lag — use lineForAXIndex: instead of lineRangeForRange:
postAccessibilityNotificationsForFrame: was calling NSString
lineRangeForRange: to detect line-crossing cursor moves.  That
method scans from the start of the string, making it O(cursor_offset).
In large buffers with the cursor near the end, this executes on every
redisplay cycle — causing progressive slowdown proportional to cursor
position.

patch 0002 already builds lineStartOffsets in ensureTextCache (O(N)
once per text change) and exposes lineForAXIndex: (O(log L) binary
search).  Use it instead.

Also remove the now-redundant tlen clamping that existed solely
to prevent lineRangeForRange: from receiving an out-of-range index.
lineForAXIndex: handles out-of-range inputs safely.
2026-03-01 08:29:58 +01:00
c4975c3fe4 patches: fix Corfu Zoom tracking — replace rate-limit with parent-frame guard
Root cause: the 50ms rate limit broke child-frame (Corfu) tracking.
When the Corfu child frame redraws, its ns_update_end fires first and
resets the rate-limit timer.  When the parent frame's ns_update_end
fires immediately after, the timer has not expired, so
ns_zoom_track_completion returns early without scanning child frames.
Zoom focus stays on the first candidate.

Fix: remove the rate limit; add a FRAME_PARENT_FRAME(f) guard instead.
Child frames have no completion children to scan; their parent's
ns_update_end does the scan via FOR_EACH_FRAME.  Returning early on
child-frame calls avoids the redundant scan and leaves the timer
problem moot.  Overhead without the rate limit is ~40 Lisp evaluations
per redisplay (~5-20 µs), acceptable given ns_zoom_enabled_p() already
caches the UAZoomEnabled() IPC call.
2026-03-01 07:11:00 +01:00
19cc43dbbb Revert "patches: fix Corfu completion tracking in Zoom"
This reverts commit d15fe43bf0.
2026-03-01 07:05:33 +01:00
d15fe43bf0 patches: fix Corfu completion tracking in Zoom
Fget_char_property with a buffer as OBJECT checks text properties
only.  Corfu highlights the selected candidate (corfu-current) via
an overlay, not a text property, so the scan always returned -1 and
Zoom focus stayed on the first line.

Pass cf->selected_window instead of cw->contents so that overlays
are included in the property lookup.  Vertico uses text properties
and is unaffected; child-frame completion frameworks that use overlays
(Corfu, Company-box) now track correctly.
2026-03-01 06:50:46 +01:00
636545c2a5 patches: auto-detect Zoom/VoiceOver; single variable gates both
Changes:
- EmacsApp gets ns_update_accessibility_state and
  ns_accessibility_did_change: methods (patch 0005)
- At startup: UAZoomEnabled() + AXIsProcessTrustedWithOptions()
  determine initial ns_accessibility_enabled state
- com.apple.accessibility.api distributed notification updates it
  whenever any AT connects or disconnects
- All Zoom call sites (UAZoomChangeFocus) now gated by
  ns_accessibility_enabled in addition to ns_zoom_enabled_p()
- ns-accessibility-enabled docstring updated to describe auto-detect

Result: zero config needed; zero overhead when no AT is active;
single variable overrides auto-detection when needed.
2026-03-01 06:39:37 +01:00
cc7b288e99 patches: reduce completion tracking rate-limit from 500ms to 50ms
500ms (2 Hz) was too aggressive — Zoom focus stopped updating during
keyboard navigation in Vertico/Corfu lists.  50ms (20 Hz) tracks
fast arrow-key navigation while still avoiding per-frame overhead.
UAZoomEnabled() is already cached so the main cost is the overlay
scan, which is cheap.
2026-03-01 06:30:01 +01:00
63f0e899ce patches: fix childFrameLastBuffer ivar init order
The Qnil initialization was in patch 0000 (Zoom) but the ivar
declaration is in patch 0008 (child frame tracking).  Moved the
init to patch 0008 so each patch compiles independently.
2026-03-01 06:04:22 +01:00
07826b61a0 patches: squash perf fixes into respective patches, clean 9-patch series
Performance fixes folded back:
- BUF_CHARS_MODIFF → patch 0002 (implement buffer accessibility element)
- UAZoomEnabled cache + rate-limit → patch 0000 (Zoom integration)

Also in patch 0000: ns_zoom_face_is_selected (standalone compilation).
Also in patch 0001: ns_accessibility_enabled defaults to nil.
2026-03-01 05:58:42 +01:00
1bf05f1e22 patches: remove duplicate old perf patch 2026-03-01 05:51:08 +01:00
256263343d patches: fix standalone compilation + accessibility default + perf
Three fixes:
1. Patch 0000 now compiles standalone: replaced forward declaration
   of ns_ax_face_is_selected (defined in VoiceOver patches) with
   self-contained ns_zoom_face_is_selected in the Zoom patch.

2. ns_accessibility_enabled defaults to nil: eliminates ALL VoiceOver
   overhead (text cache rebuild, AX notifications, Mach IPC to AX
   server) when VoiceOver is not in use. Zero per-redisplay cost.
   Enable with (setq ns-accessibility-enabled t).

3. UAZoomEnabled() cached for 1s + ns_zoom_track_completion rate-
   limited to 2Hz: eliminates 150-600µs/frame of IPC overhead.
2026-03-01 05:51:03 +01:00
6b3843e0c6 patches: fix O(position) performance via UAZoomEnabled caching
Root cause (per Opus analysis): UAZoomEnabled() is a synchronous
Mach IPC roundtrip to macOS Accessibility server, called 3x per
redisplay cycle. At 60fps = 180 IPC roundtrips/second blocking the
main thread. Combined with Emacs's inherent O(position) redisplay
cost, this compounded into progressive choppy behavior.

Fix 1: ns_zoom_enabled_p() caches UAZoomEnabled() for 1 second.
Fix 2: ns_zoom_track_completion() rate-limited to 2 Hz.

Also includes BUF_CHARS_MODIFF fix (patch 0009) for VoiceOver cache.
2026-03-01 05:23:59 +01:00
cd16d45584 patches: fix O(buffer) cache invalidation caused by font-lock
BUF_CHARS_MODIFF fix — the core performance regression:
ensureTextCache checked BUF_MODIFF which font-lock bumps on every
redisplay. Each cursor movement in a large file triggered full buffer
rebuild. Now uses BUF_CHARS_MODIFF (changes only on char insert/delete).
2026-03-01 04:56:37 +01:00
bc71e58123 patches: fix two more compile errors
- Remove f->child_frame_list (field does not exist in struct frame)
- Fix dangling else-if after goto label (skip_overlay_scan)
2026-03-01 04:33:35 +01:00
3d2fa7a54e patches: fix O(overlays) performance regression
Performance issue: editing large files (>~10KB, >2000 lines) caused
progressive slowdown regardless of VoiceOver status.

Root causes:
1. ns_zoom_find_overlay_candidate_line: called Foverlays_in on the
   entire visible buffer range on every redisplay when UAZoomEnabled().
   In files with many overlays (font-lock, hl-line, show-paren etc.)
   this was O(overlays) Lisp work per keystroke.

2. postAccessibilityNotificationsForFrame: when ns-accessibility-enabled
   is non-nil, checked BUF_OVERLAY_MODIFF every redisplay. font-lock
   bumps this on every redraw, triggering ns_ax_selected_overlay_text
   (another O(overlays) scan) for non-minibuffer windows.

Fix: Both scans now guard with MINI_WINDOW_P check. Overlay completion
frameworks (Vertico, Icomplete, Ivy) only display candidates in
minibuffer windows --- no completion framework puts selected-face
overlays in normal editing buffers. For non-minibuffer windows both
functions return immediately with zero Lisp calls.

Additionally: ns_zoom_find_child_frame_candidate is skipped when
f->child_frame_list is nil (no child frames = no Corfu popup).
2026-03-01 04:26:12 +01:00
84eb777065 patches: fix all compile errors and review issues
- ZV_S -> BUF_ZV (undefined macro)
- cf->current_buffer -> XWINDOW(cf->selected_window)->contents
  (current_buffer is a thread macro, can't use as field name)
- find_newline: add record_unwind_current_buffer + set_buffer_internal_1
- ns_zoom_track_completion: add specpdl unwind protection
- ns_zoom_face_is_selected: replace with forward decl of ns_ax_face_is_selected
  (eliminates duplicate)
- childFrameLastBuffer: struct buffer * -> Lisp_Object (safe vs kill-buffer)
- EmacsView dealloc: xfree childFrameLastCandidate (memory leak)
- postCompletionAnnouncementForBuffer: add block_input/unblock_input
2026-03-01 03:58:04 +01:00
b283068f82 patches: add Zoom completion tracking (overlay + child frame)
Zoom patch 0000 now tracks completion candidates:
- Overlay: Vertico, Icomplete, Ivy (face heuristic on before-string)
- Child frame: Corfu, Company-box (scan buffer text for selected face)
Also fixes duplicate lastCursorRect ivar when applied with VoiceOver.
2026-03-01 03:38:58 +01:00
9110eee881 patches: fix duplicate lastCursorRect ivar (build error)
Zoom (0000) declares lastCursorRect @public in EmacsView.
VoiceOver (0005) was re-declaring it, causing 'duplicate member'
compiler error when both applied together. Removed the duplicate.
2026-03-01 03:20:23 +01:00
74fcee0820 patches: regenerate for combined application (Zoom + VoiceOver)
VoiceOver patches 0001-0008 now apply cleanly on top of Zoom patch
0000.  The full set (git am patches/000*.patch) works without
conflicts.  Patch 0005 (integration) merges Zoom fallback and
VoiceOver postAccessibilityUpdates in ns_update_end.
2026-03-01 03:02:46 +01:00