Commit Graph

41 Commits

Author SHA1 Message Date
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
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
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
132e32795f patches: review iteration 1 fixes
Shortened ivar comments (line length), broke long ObjC method call,
changed '---' to em-dash in overlay patch comment.
2026-03-01 02:42:49 +01:00
98ca6a378d patches: move lastCursorRect ivar from patch 1 to patch 5
The ivar was declared in patch 0001 but first used in patch 0005,
creating dead code in intermediate commits 0001-0004.  Now each
commit only introduces declarations that are immediately used.
2026-02-28 22:55:31 +01:00
61b5b5daf1 patches: fix windowWillResize em-dash regression (iteration 3)
Patch 0007 bulk em-dash→triple-dash replacement accidentally changed
windowWillResize title format string and strstr search, introducing
a user-visible regression. Reverted those two lines to em-dash.
2026-02-28 22:45:05 +01:00
9d2b1da729 patches: fix all review blockers (iteration 2)
Fixes from Opus maintainer review:
1. [BLOCKER] Zoom code completely removed from ALL intermediate patches
   (0005-0007 no longer have UAZoom/overlayZoom at any commit point)
2. [BLOCKER] Unified cursor rect ivar: lastCursorRect (was split
   between lastZoomCursorRect and lastAccessibilityCursorRect)
3. [HIGH] Child frame static vars moved to EmacsView ivars
   (childFrameLastCandidate/Buffer/Modiff — no cross-frame interference)
4. [HIGH] intern_c_string replaced with Qbefore_string/Qafter_string
5. [MEDIUM] Zoom fallback gated by zoomCursorUpdated flag (no double call)
2026-02-28 22:39:57 +01:00
d9b4cbb87a patches: restructure per reviewer feedback
Major changes:
1. Zoom separated into standalone patch 0000
   - UAZoomChangeFocus in ns_draw_window_cursor
   - Fallback in ns_update_end for window-switch tracking
   - No overlayZoomActive (source of split/switch/move bug)

2. VoiceOver patches 0001-0008 are now Zoom-free
   - All UAZoom*, overlayZoom*, kUAZoomFocus references removed
   - lastAccessibilityCursorRect kept for VoiceOver bounds queries
   - Commit messages cleaned of Zoom references

3. README.txt and TESTING.txt rewritten for new structure

Addresses reviewer (Stéphane Marks) feedback:
- Keep Zoom patch separate from VoiceOver work
- Design discussion needed for non-Zoom patches
- Performance: ns-accessibility-enabled=nil for zero overhead
2026-02-28 22:28:35 +01:00
bbe683e752 patches: fix 3 blockers — duplicate functions, idx typo, doc cap
BLOCKER fixes:
1. Remove duplicate ns_ax_face_is_selected, ns_ax_selected_overlay_text,
   ns_ax_selected_child_frame_text definitions from patch 0002
   (now defined only in 0007/0008 where they belong)
2. Fix idx → point_idx in accessibilityInsertionPointLineNumber (0002)
3. Remove stale 100K cap reference from documentation (0006)

Architecture fix:
- ns_ax_selected_child_frame_text moved from 0007 to 0008
  (where it logically belongs)

Verified: all 8 patches apply cleanly on fresh emacs HEAD.
2026-02-28 22:00:10 +01:00
30089e9413 patches: fold line index + remove NS_AX_TEXT_CAP into 0001-0002
- 0001: remove NS_AX_TEXT_CAP (100K char cap), add lineStartOffsets/
  lineCount ivars and method declarations to nsterm.h
- 0002: add lineForAXIndex:/rangeForLine: O(log L) helpers, build line
  index in ensureTextCache, replace O(L) line scanning in
  accessibilityInsertionPointLineNumber/accessibilityLineForIndex/
  accessibilityRangeForLine, free index in invalidateTextCache/dealloc
- 0009 deleted (folded into 0001+0002)
- README.txt: remove NS_AX_TEXT_CAP references, update known
  limitations, stress test threshold 50K lines
2026-02-28 21:39:30 +01:00
Martin Sukany
98b3d04597 Revert "patches: add 0009 resource safety hardening + update 0007/0008"
This reverts commit acc2a2985e.
2026-02-28 19:05:02 +01:00
acc2a2985e patches: add 0009 resource safety hardening + update 0007/0008
New patch 0009 fixes HIGH severity issues from Opus review:
- Announcement coalescing (50ms debounce)
- cachedText retain+autorelease in accessibilityValue
- EmacsView dealloc: nil out emacsView on all AX elements
- Nil guards on protocol methods + overlayZoomActive

0007 updated: revert accidental em-dash→triple-dash, add overlayZoomActive nil guards
0008 updated: specpdl exception safety for accessibilityUpdating, lastChildFrameBuffer staticpro

Series now 9 patches total (0001-0006 unchanged, 0007-0009 new/updated).
2026-02-28 18:45:30 +01:00
0f7608326c patches: fix 2 blockers from Opus review
BLOCKER #1: accessibilityUpdating flag exception safety.
A Lisp signal (longjmp) during postAccessibilityUpdates left
the re-entrance flag permanently YES, suppressing all future
AX notifications → VoiceOver goes silent randomly.
Fix: specpdl unwind protection (record_unwind_protect_ptr)
resets the flag on any longjmp. All 3 exit points use unbind_to.

BLOCKER #2: static struct buffer *lastBuffer dangling pointer.
Raw C pointer to buffer struct has no GC protection. After
kill-buffer, the pointer dangles.
Fix: file-scope Lisp_Object lastChildFrameBuffer with staticpro.
EQ comparison instead of pointer equality.

Also: revert accidental em-dash → triple-dash in title bar (0007),
fix README factual error (BUF_OVERLAY_MODIFF cache key).
2026-02-28 18:29:19 +01:00
c05b46b058 patches: 0007 textDidChange fix (hl-line-mode), remove config workaround 2026-02-28 17:42:00 +01:00
beb5e14adf patches: revert textDidChange from 0007, config workarounds
- 0007 reverted to original else-if (no textDidChange flag)
- config: disable hl-line-mode (BUF_MODIFF blocking VoiceOver)
- config: org-startup-folded 'content (all headings, body hidden)
- config: corfu-auto-prefix 3 + delay re-set hook after Doom init
2026-02-28 17:39:51 +01:00
edbed0a116 patches: 0007 fix hl-line-mode blocking SelectedTextChanged
hl-line-mode (and similar) bumps BUF_MODIFF via text property
changes on every cursor movement. The else-if structure caused
the modiff branch to fire (skipping ValueChanged correctly) but
also blocked the cursor-move branch (SelectedTextChanged).

Fix: use textDidChange flag to decouple the two branches.
ValueChanged and SelectedTextChanged remain mutually exclusive
for real edits, but SelectedTextChanged now fires when only
text properties changed.
2026-02-28 17:38:05 +01:00
9772b7e33e patches: drop 0008 (child frame), revert 0007 to clean state
Child frame completion (Corfu) will be handled via config:
corfu-terminal-mode renders as overlays → patch 0007 handles it.
2026-02-28 16:48:53 +01:00
a8af58cff1 patches: split 0007 (overlay) + 0008 (child frame)
0007: Vertico/Icomplete/Ivy overlay completion, Zoom at text left edge
0008: Corfu/Company-box child frame completion, direct UAZoomChangeFocus
2026-02-28 16:02:13 +01:00
92188ab008 patches: v8 0007 - child frame completion (Corfu) + Zoom fix
Added: child frame buffer scanning for Corfu/Company-box.
Fixed: Zoom rect at text area left edge (not window center).
Added: 'selection' face match for company-tooltip-selection.
2026-02-28 15:59:11 +01:00
be4e0bb5be patches: v7 0007 - Zoom left edge + selection face match
Zoom rect now at text area left edge (WINDOW_TEXT_TO_FRAME_PIXEL_X)
with cursor-width (FRAME_COLUMN_WIDTH) instead of full window width.
Face matching adds 'selection' (company-tooltip-selection).
2026-02-28 15:51:06 +01:00
4e5596d9de patches: v6 0007 - fix Zoom Y offset (line_height arithmetic) 2026-02-28 15:39:44 +01:00
9129f032cf patches: v5 0007 - review fixes (em dash, comments, safety docs) 2026-02-28 15:36:50 +01:00
99609f0437 patches: v4 0007 - Zoom follows overlay candidate
Zoom now tracks the selected candidate row via overlayZoomRect
instead of always pointing at the text cursor. Returns to cursor
on typing (chars_modiff change).
2026-02-28 15:33:45 +01:00
9359277143 patches: v3 0007 - face name heuristic for candidate detection
Previous two-reference algorithm failed because:
- Vertico's cursor-space line (face=nil) confused the reference
- Count overlay processed before candidates overlay
- Group titles have distinct faces too

New approach: ns_ax_face_is_selected checks if face symbol name
contains 'current' or 'selected'. Works for all major frameworks
(Vertico, Icomplete, Ivy) without framework-specific code.
2026-02-28 15:25:52 +01:00
fcff3429b1 patches: rewrite 0007 - fix root cause of 'new line' announcement
Root cause: Vertico bumps BOTH BUF_MODIFF (text property face change
from vertico--prompt-selection) and BUF_OVERLAY_MODIFF (overlay-put)
in same cycle. Previous else-if chain meant overlay branch never fired.

Fixes:
1. Overlay check independent (if, not else-if)
2. BUF_CHARS_MODIFF gates ValueChanged (suppress property-only changes)
3. ensureTextCache no longer tracks overlay_modiff (prevents race)
4. Only AnnouncementRequested (no SelectedTextChanged - wrong line)
5. Two-reference face detection + single-candidate
6. Zoom tracking via UAZoomChangeFocus
2026-02-28 15:12:11 +01:00
9408e37a90 patches: rewrite 0007 overlay support
Key changes from previous version:
- Remove overlay text from ns_ax_buffer_text (was causing spurious
  'new line' announcements via VoiceOver text diff)
- Do NOT invalidate text cache on overlay change
- Two-reference face detection (handles selected at any position)
- SDATA scan instead of per-char Faref for newline detection
- Zoom tracking via UAZoomChangeFocus for selected candidate row
- Deduplication via cachedCompletionAnnouncement
2026-02-28 14:57:00 +01:00