v15.7: fix buffer gap corruption in visible text extraction

Root cause: ns_ax_buffer_text used BUF_BYTE_ADDRESS + raw pointer
read which crosses the buffer gap when visible runs span it. The gap
follows point, so completion cycling and dired navigation reliably
trigger corruption — VoiceOver reads wrong text.

Fix: Replace raw pointer extraction with Fbuffer_substring_no_properties
which handles the gap internally. Add ax_length field to visible run
struct for accurate UTF-16 length tracking (fixes supplementary
Unicode character offset drift).

Secondary: ax_offset accumulation now uses NSString length (UTF-16
units) instead of Emacs char count, preventing progressive drift in
index mapping for subsequent visible runs.
This commit is contained in:
2026-02-26 16:07:42 +01:00
parent f09f6dd0f3
commit 25e2a21245

View File

@@ -1,8 +1,8 @@
diff --git a/src/nsterm.h b/src/nsterm.h diff --git a/src/nsterm.h b/src/nsterm.h
index 7c1ee4c..855d302 100644 index 7c1ee4c..2e2c80f 100644
--- a/src/nsterm.h --- a/src/nsterm.h
+++ b/src/nsterm.h +++ b/src/nsterm.h
@@ -453,6 +453,57 @@ enum ns_return_frame_mode @@ -453,6 +453,58 @@ enum ns_return_frame_mode
@end @end
@@ -29,8 +29,9 @@ index 7c1ee4c..855d302 100644
+typedef struct ns_ax_visible_run +typedef struct ns_ax_visible_run
+{ +{
+ ptrdiff_t charpos; /* Buffer charpos where this visible run starts. */ + ptrdiff_t charpos; /* Buffer charpos where this visible run starts. */
+ ptrdiff_t length; /* Number of visible characters in this run. */ + ptrdiff_t length; /* Number of visible Emacs characters in this run. */
+ NSUInteger ax_start; /* Starting index in the accessibility string. */ + NSUInteger ax_start; /* Starting index in the accessibility string. */
+ NSUInteger ax_length; /* Length in accessibility string (UTF-16 units). */
+} ns_ax_visible_run; +} ns_ax_visible_run;
+ +
+/* Virtual AXTextArea element — one per visible Emacs window (buffer). */ +/* Virtual AXTextArea element — one per visible Emacs window (buffer). */
@@ -60,7 +61,7 @@ index 7c1ee4c..855d302 100644
/* ========================================================================== /* ==========================================================================
The main Emacs view The main Emacs view
@@ -471,6 +522,14 @@ enum ns_return_frame_mode @@ -471,6 +523,14 @@ enum ns_return_frame_mode
#ifdef NS_IMPL_COCOA #ifdef NS_IMPL_COCOA
char *old_title; char *old_title;
BOOL maximizing_resize; BOOL maximizing_resize;
@@ -75,7 +76,7 @@ index 7c1ee4c..855d302 100644
#endif #endif
BOOL font_panel_active; BOOL font_panel_active;
NSFont *font_panel_result; NSFont *font_panel_result;
@@ -528,6 +587,13 @@ enum ns_return_frame_mode @@ -528,6 +588,13 @@ enum ns_return_frame_mode
- (void)windowWillExitFullScreen; - (void)windowWillExitFullScreen;
- (void)windowDidExitFullScreen; - (void)windowDidExitFullScreen;
- (void)windowDidBecomeKey; - (void)windowDidBecomeKey;
@@ -90,7 +91,7 @@ index 7c1ee4c..855d302 100644
diff --git a/src/nsterm.m b/src/nsterm.m diff --git a/src/nsterm.m b/src/nsterm.m
index 932d209..ca6b969 100644 index 932d209..043477b 100644
--- a/src/nsterm.m --- a/src/nsterm.m
+++ b/src/nsterm.m +++ b/src/nsterm.m
@@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f) @@ -1104,6 +1104,11 @@ ns_update_end (struct frame *f)
@@ -143,29 +144,23 @@ index 932d209..ca6b969 100644
ns_focus (f, NULL, 0); ns_focus (f, NULL, 0);
NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
@@ -6849,265 +6885,1333 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) @@ -6847,6 +6883,1074 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
}
#endif
/* ========================================================================== +/* ==========================================================================
+
- EmacsView implementation
+ Accessibility virtual elements (macOS / Cocoa only) + Accessibility virtual elements (macOS / Cocoa only)
+
========================================================================== */ + ========================================================================== */
+
+#ifdef NS_IMPL_COCOA +#ifdef NS_IMPL_COCOA
+
-@implementation EmacsView
+/* ---- Helper: extract buffer text for accessibility ---- */ +/* ---- Helper: extract buffer text for accessibility ---- */
+
-- (void)windowDidEndLiveResize:(NSNotification *)notification
-{
- [self updateFramePosition];
-}
+/* Maximum characters exposed via accessibilityValue. */ +/* Maximum characters exposed via accessibilityValue. */
+#define NS_AX_TEXT_CAP 100000 +#define NS_AX_TEXT_CAP 100000
+
-/* Needed to inform when window closed from lisp. */
-- (void) setWindowClosing: (BOOL)closing
+/* Build accessibility text for window W, skipping invisible text. +/* Build accessibility text for window W, skipping invisible text.
+ Populates *OUT_START with the buffer start charpos. + Populates *OUT_START with the buffer start charpos.
+ Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS + Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
@@ -174,71 +169,45 @@ index 932d209..ca6b969 100644
+static NSString * +static NSString *
+ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start, +ns_ax_buffer_text (struct window *w, ptrdiff_t *out_start,
+ ns_ax_visible_run **out_runs, NSUInteger *out_nruns) + ns_ax_visible_run **out_runs, NSUInteger *out_nruns)
{ +{
- NSTRACE ("[EmacsView setWindowClosing:%d]", closing);
+ *out_runs = NULL; + *out_runs = NULL;
+ *out_nruns = 0; + *out_nruns = 0;
+
- windowClosing = closing;
-}
+ if (!w || !WINDOW_LEAF_P (w)) + if (!w || !WINDOW_LEAF_P (w))
+ { + {
+ *out_start = 0; + *out_start = 0;
+ return @""; + return @"";
+ } + }
+
+ struct buffer *b = XBUFFER (w->contents); + struct buffer *b = XBUFFER (w->contents);
+ if (!b) + if (!b)
+ { + {
+ *out_start = 0; + *out_start = 0;
+ return @""; + return @"";
+ } + }
+
-- (void)dealloc
-{
- NSTRACE ("[EmacsView dealloc]");
+ ptrdiff_t begv = BUF_BEGV (b); + ptrdiff_t begv = BUF_BEGV (b);
+ ptrdiff_t zv = BUF_ZV (b); + ptrdiff_t zv = BUF_ZV (b);
+
- /* Clear the view resize notification. */
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:NSViewFrameDidChangeNotification
- object:nil];
+ *out_start = begv; + *out_start = begv;
+
- if (fs_state == FULLSCREEN_BOTH)
- [nonfs_window release];
+ if (zv <= begv) + if (zv <= begv)
+ return @""; + return @"";
+
-#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
- /* Release layer and menu */
- EmacsLayer *layer = (EmacsLayer *)[self layer];
- [layer release];
-#endif
+ 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);
+
- [[self menu] release];
- [super dealloc];
-}
+ /* First pass: count visible runs to allocate the mapping array. */ + /* First pass: count visible runs to allocate the mapping array. */
+ NSUInteger run_capacity = 64; + NSUInteger run_capacity = 64;
+ ns_ax_visible_run *runs = xmalloc (run_capacity + ns_ax_visible_run *runs = xmalloc (run_capacity
+ * sizeof (ns_ax_visible_run)); + * sizeof (ns_ax_visible_run));
+ NSUInteger nruns = 0; + NSUInteger nruns = 0;
+ NSUInteger ax_offset = 0; + NSUInteger ax_offset = 0;
+
+ NSMutableString *result = [NSMutableString string]; + NSMutableString *result = [NSMutableString string];
+ ptrdiff_t pos = begv; + ptrdiff_t pos = begv;
+
-/* Called on font panel selection. */
-- (void) changeFont: (id) sender
-{
- struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
- NSFont *nsfont;
+ while (pos < zv) + while (pos < zv)
+ { + {
+ /* Check invisible property (text properties + overlays). */ + /* Check invisible property (text properties + overlays). */
@@ -258,19 +227,12 @@ index 932d209..ca6b969 100644
+ pos = FIXNUMP (next) ? XFIXNUM (next) : zv; + pos = FIXNUMP (next) ? XFIXNUM (next) : zv;
+ continue; + continue;
+ } + }
+
-#ifdef NS_IMPL_GNUSTEP
- nsfont = ((struct nsfont_info *) font)->nsfont;
-#else
- nsfont = (NSFont *) macfont_get_nsctfont (font);
-#endif
+ /* Find end of this visible run: where invisible property changes. */ + /* Find end of this visible run: where invisible property changes. */
+ Lisp_Object next = Fnext_single_char_property_change ( + Lisp_Object next = Fnext_single_char_property_change (
+ make_fixnum (pos), Qinvisible, Qnil, make_fixnum (zv)); + make_fixnum (pos), Qinvisible, Qnil, make_fixnum (zv));
+ ptrdiff_t run_end = FIXNUMP (next) ? XFIXNUM (next) : zv; + ptrdiff_t run_end = FIXNUMP (next) ? XFIXNUM (next) : zv;
+
- if (!font_panel_active)
- return;
+ /* Cap total text at NS_AX_TEXT_CAP. */ + /* Cap total text at NS_AX_TEXT_CAP. */
+ ptrdiff_t run_len = run_end - pos; + ptrdiff_t run_len = run_end - pos;
+ if (ax_offset + (NSUInteger) run_len > NS_AX_TEXT_CAP) + if (ax_offset + (NSUInteger) run_len > NS_AX_TEXT_CAP)
@@ -278,22 +240,17 @@ index 932d209..ca6b969 100644
+ if (run_len <= 0) + if (run_len <= 0)
+ break; + break;
+ run_end = pos + run_len; + run_end = pos + run_len;
+
- if (font_panel_result) + /* Extract this visible run's text. Use
- [font_panel_result release]; + Fbuffer_substring_no_properties which correctly handles the
+ /* Extract this visible run's text. */ + buffer gap — raw BUF_BYTE_ADDRESS reads across the gap would
+ ptrdiff_t pos_byte = buf_charpos_to_bytepos (b, pos); + include garbage bytes when the run spans the gap position. */
+ ptrdiff_t end_byte = buf_charpos_to_bytepos (b, run_end); + Lisp_Object lstr = Fbuffer_substring_no_properties (
+ unsigned char *data = BUF_BYTE_ADDRESS (b, pos_byte); + make_fixnum (pos), make_fixnum (run_end));
+ ptrdiff_t nbytes = end_byte - pos_byte; + NSString *nsstr = [NSString stringWithLispString:lstr];
+ NSUInteger ns_len = [nsstr length];
- font_panel_result = (NSFont *) [sender convertFont: nsfont]; + [result appendString:nsstr];
+ Lisp_Object lstr = make_string_from_bytes ((char *) data, +
+ run_len, nbytes);
+ [result appendString:[NSString stringWithLispString:lstr]];
- if (font_panel_result)
- [font_panel_result retain];
+ /* Record this visible run in the mapping. */ + /* Record this visible run in the mapping. */
+ if (nruns >= run_capacity) + if (nruns >= run_capacity)
+ { + {
@@ -304,13 +261,10 @@ index 932d209..ca6b969 100644
+ runs[nruns].charpos = pos; + runs[nruns].charpos = pos;
+ runs[nruns].length = run_len; + runs[nruns].length = run_len;
+ runs[nruns].ax_start = ax_offset; + runs[nruns].ax_start = ax_offset;
+ runs[nruns].ax_length = ns_len;
+ nruns++; + nruns++;
+
-#ifndef NS_IMPL_COCOA + ax_offset += ns_len;
- font_panel_active = NO;
- [NSApp stop: self];
-#endif
+ ax_offset += (NSUInteger) run_len;
+ pos = run_end; + pos = run_end;
+ } + }
+ +
@@ -320,36 +274,26 @@ index 932d209..ca6b969 100644
+ *out_runs = runs; + *out_runs = runs;
+ *out_nruns = nruns; + *out_nruns = nruns;
+ return result; + return result;
} +}
+
-#ifdef NS_IMPL_COCOA
-- (void) noteUserSelectedFont
+ +
+/* ---- Helper: extract mode line text from glyph rows ---- */ +/* ---- Helper: extract mode line text from glyph rows ---- */
+ +
+static NSString * +static NSString *
+ns_ax_mode_line_text (struct window *w) +ns_ax_mode_line_text (struct window *w)
{ +{
- font_panel_active = NO;
+ if (!w || !w->current_matrix) + if (!w || !w->current_matrix)
+ return @""; + return @"";
+
- /* If no font was previously selected, use the currently selected
- font. */
+ struct glyph_matrix *matrix = w->current_matrix; + struct glyph_matrix *matrix = w->current_matrix;
+ NSMutableString *text = [NSMutableString string]; + NSMutableString *text = [NSMutableString string];
+
- if (!font_panel_result && FRAME_FONT (emacsframe))
+ for (int i = 0; i < matrix->nrows; i++) + for (int i = 0; i < matrix->nrows; i++)
{ + {
- font_panel_result
- = macfont_get_nsctfont (FRAME_FONT (emacsframe));
+ struct glyph_row *row = matrix->rows + i; + struct glyph_row *row = matrix->rows + i;
+ if (!row->enabled_p || !row->mode_line_p) + if (!row->enabled_p || !row->mode_line_p)
+ continue; + continue;
+
- if (font_panel_result)
- [font_panel_result retain];
+ struct glyph *g = row->glyphs[TEXT_AREA]; + struct glyph *g = row->glyphs[TEXT_AREA];
+ struct glyph *end = g + row->used[TEXT_AREA]; + struct glyph *end = g + row->used[TEXT_AREA];
+ for (; g < end; g++) + for (; g < end; g++)
@@ -361,64 +305,28 @@ index 932d209..ca6b969 100644
+ length:1]]; + length:1]];
+ } + }
+ } + }
} + }
-
- [NSApp stop: self];
+ return text; + return text;
} +}
+
-- (void) noteUserCancelledSelection +
-{
- font_panel_active = NO;
-
- if (font_panel_result)
- [font_panel_result release];
- font_panel_result = nil;
- [NSApp stop: self];
-}
-#endif
+/* ---- Helper: screen rect for a character range via glyph matrix ---- */ +/* ---- Helper: screen rect for a character range via glyph matrix ---- */
+
-- (Lisp_Object) showFontPanel
+static NSRect +static NSRect
+ns_ax_frame_for_range (struct window *w, EmacsView *view, +ns_ax_frame_for_range (struct window *w, EmacsView *view,
+ ptrdiff_t text_start, NSRange range) + ptrdiff_t text_start, NSRange range)
{ +{
- id fm = [NSFontManager sharedFontManager];
- struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
- NSFont *nsfont, *result;
- struct timespec timeout;
-#ifdef NS_IMPL_COCOA
- NSView *buttons;
- BOOL canceled;
-#endif
+ if (!w || !w->current_matrix || !view) + if (!w || !w->current_matrix || !view)
+ return NSZeroRect; + return NSZeroRect;
+
-#ifdef NS_IMPL_GNUSTEP
- nsfont = ((struct nsfont_info *) font)->nsfont;
-#else
- nsfont = (NSFont *) macfont_get_nsctfont (font);
-#endif
+ /* Convert range indices back to buffer charpos. */ + /* Convert range indices back to buffer charpos. */
+ ptrdiff_t cp_start = text_start + (ptrdiff_t) range.location; + ptrdiff_t cp_start = text_start + (ptrdiff_t) range.location;
+ ptrdiff_t cp_end = cp_start + (ptrdiff_t) range.length; + ptrdiff_t cp_end = cp_start + (ptrdiff_t) range.length;
+
-#ifdef NS_IMPL_COCOA
- buttons
- = ns_create_font_panel_buttons (self,
- @selector (noteUserSelectedFont),
- @selector (noteUserCancelledSelection));
- [[fm fontPanel: YES] setAccessoryView: buttons];
- [buttons release];
-#endif
+ struct glyph_matrix *matrix = w->current_matrix; + struct glyph_matrix *matrix = w->current_matrix;
+ NSRect result = NSZeroRect; + NSRect result = NSZeroRect;
+ BOOL found = NO; + BOOL found = NO;
+
- [fm setSelectedFont: nsfont isMultiple: NO];
- [fm orderFrontFontPanel: NSApp];
+ for (int i = 0; i < matrix->nrows; i++) + for (int i = 0; i < matrix->nrows; i++)
+ { + {
+ struct glyph_row *row = matrix->rows + i; + struct glyph_row *row = matrix->rows + i;
@@ -426,22 +334,10 @@ index 932d209..ca6b969 100644
+ continue; + continue;
+ if (!row->displays_text_p && !row->ends_at_zv_p) + if (!row->displays_text_p && !row->ends_at_zv_p)
+ continue; + continue;
+
- font_panel_active = YES;
- timeout = make_timespec (0, 100000000);
+ ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row); + ptrdiff_t row_start = MATRIX_ROW_START_CHARPOS (row);
+ ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row); + ptrdiff_t row_end = MATRIX_ROW_END_CHARPOS (row);
+
- block_input ();
- while (font_panel_active
-#ifdef NS_IMPL_COCOA
- && (canceled = [[fm fontPanel: YES] isVisible])
-#else
- && [[fm fontPanel: YES] isVisible]
-#endif
- )
- ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES);
- unblock_input ();
+ if (row_start < cp_end && row_end > cp_start) + if (row_start < cp_end && row_end > cp_start)
+ { + {
+ int window_x, window_y, window_width; + int window_x, window_y, window_width;
@@ -464,16 +360,10 @@ index 932d209..ca6b969 100644
+ result = NSUnionRect (result, rowRect); + result = NSUnionRect (result, rowRect);
+ } + }
+ } + }
+
- if (font_panel_result)
- [font_panel_result autorelease];
+ if (!found) + if (!found)
+ return NSZeroRect; + return NSZeroRect;
+
-#ifdef NS_IMPL_COCOA
- if (!canceled)
- font_panel_result = nil;
-#endif
+ /* Clip result to text area bounds. */ + /* Clip result to text area bounds. */
+ { + {
+ int text_area_x, text_area_y, text_area_w, text_area_h; + int text_area_x, text_area_y, text_area_w, text_area_h;
@@ -483,23 +373,15 @@ index 932d209..ca6b969 100644
+ if (NSMaxY (result) > max_y) + if (NSMaxY (result) > max_y)
+ result.size.height = max_y - result.origin.y; + result.size.height = max_y - result.origin.y;
+ } + }
+
- result = font_panel_result;
- font_panel_result = nil;
+ /* Convert from EmacsView (flipped) coords to screen coords. */ + /* Convert from EmacsView (flipped) coords to screen coords. */
+ NSRect winRect = [view convertRect:result toView:nil]; + NSRect winRect = [view convertRect:result toView:nil];
+ return [[view window] convertRectToScreen:winRect]; + return [[view window] convertRectToScreen:winRect];
+} +}
+
- [[fm fontPanel: YES] setIsVisible: NO]; +
- font_panel_active = NO;
- if (result)
- return ns_font_desc_to_font_spec ([result fontDescriptor],
- result);
+@implementation EmacsAccessibilityElement +@implementation EmacsAccessibilityElement
+
- return Qnil;
+- (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
+{ +{
+ EmacsView *view = self.emacsView; + EmacsView *view = self.emacsView;
@@ -509,55 +391,33 @@ index 932d209..ca6b969 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)acceptsFirstResponder
+- (BOOL)isAccessibilityElement +- (BOOL)isAccessibilityElement
{ +{
- NSTRACE ("[EmacsView acceptsFirstResponder]"); + return YES;
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) ---- */ +/* ---- Hierarchy plumbing (required for VoiceOver to find us) ---- */
+ +
+- (id)accessibilityParent +- (id)accessibilityParent
{ +{
- NSTRACE_MSG ("First mouse event: type=%ld, clickCount=%ld",
- [theEvent type], [theEvent clickCount]);
- return ns_click_through;
+ return NSAccessibilityUnignoredAncestor (self.emacsView); + return NSAccessibilityUnignoredAncestor (self.emacsView);
} +}
-- (void)resetCursorRects
+ +
+- (id)accessibilityWindow +- (id)accessibilityWindow
{ +{
- NSRect visible = [self visibleRect];
- NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe);
- NSTRACE ("[EmacsView resetCursorRects]");
+ return [self.emacsView window]; + return [self.emacsView window];
+} +}
+
- if (currentCursor == nil)
- currentCursor = [NSCursor arrowCursor];
+- (id)accessibilityTopLevelUIElement +- (id)accessibilityTopLevelUIElement
+{ +{
+ return [self.emacsView window]; + return [self.emacsView window];
+} +}
+
- if (!NSIsEmptyRect (visible))
- [self addCursorRect: visible cursor: currentCursor];
+@end +@end
+
-#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
-}
+@implementation EmacsAccessibilityBuffer +@implementation EmacsAccessibilityBuffer
+@synthesize cachedText; +@synthesize cachedText;
+@synthesize cachedTextModiff; +@synthesize cachedTextModiff;
@@ -565,7 +425,7 @@ index 932d209..ca6b969 100644
+@synthesize cachedModiff; +@synthesize cachedModiff;
+@synthesize cachedPoint; +@synthesize cachedPoint;
+@synthesize cachedMarkActive; +@synthesize cachedMarkActive;
+
+- (void)dealloc +- (void)dealloc
+{ +{
+ [cachedText release]; + [cachedText release];
@@ -573,20 +433,11 @@ index 932d209..ca6b969 100644
+ xfree (visibleRuns); + xfree (visibleRuns);
+ [super dealloc]; + [super dealloc];
+} +}
+
-/*****************************************************************************/
-/* Keyboard handling. */
-#define NS_KEYLOG 0
+/* ---- Text cache ---- */ +/* ---- Text cache ---- */
+
-- (void)keyDown: (NSEvent *)theEvent
+- (void)invalidateTextCache +- (void)invalidateTextCache
{ +{
- Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
- int code;
- unsigned fnKeysym = 0;
- static NSMutableArray *nsEvArray;
- unsigned int flags = [theEvent modifierFlags];
+ [cachedText release]; + [cachedText release];
+ cachedText = nil; + cachedText = nil;
+ if (visibleRuns) + if (visibleRuns)
@@ -596,21 +447,17 @@ index 932d209..ca6b969 100644
+ } + }
+ visibleRunCount = 0; + visibleRunCount = 0;
+} +}
+
- NSTRACE ("[EmacsView keyDown:]");
+- (void)ensureTextCache +- (void)ensureTextCache
+{ +{
+ struct window *w = self.emacsWindow; + struct window *w = self.emacsWindow;
+ if (!w || !WINDOW_LEAF_P (w)) + if (!w || !WINDOW_LEAF_P (w))
+ return; + return;
+
- /* Rhapsody and macOS give up and down events for the arrow keys. */
- if ([theEvent type] != NSEventTypeKeyDown)
+ struct buffer *b = XBUFFER (w->contents); + struct buffer *b = XBUFFER (w->contents);
+ if (!b) + if (!b)
return; + return;
+
- if (!emacs_event)
+ ptrdiff_t modiff = BUF_MODIFF (b); + ptrdiff_t modiff = BUF_MODIFF (b);
+ ptrdiff_t pt = BUF_PT (b); + ptrdiff_t pt = BUF_PT (b);
+ NSUInteger textLen = cachedText ? [cachedText length] : 0; + NSUInteger textLen = cachedText ? [cachedText length] : 0;
@@ -618,53 +465,31 @@ index 932d209..ca6b969 100644
+ && pt >= cachedTextStart + && pt >= cachedTextStart
+ && (textLen == 0 + && (textLen == 0
+ || [self accessibilityIndexForCharpos:pt] <= textLen)) + || [self accessibilityIndexForCharpos:pt] <= textLen))
return; + return;
+
- if (![[self window] isKeyWindow]
- && [[theEvent window] isKindOfClass: [EmacsWindow class]]
- /* We must avoid an infinite loop here. */
- && (EmacsView *)[[theEvent window] delegate] != self)
- {
- /* XXX: There is an occasional condition in which, when Emacs display
- updates a different frame from the current one, and temporarily
- selects it, then processes some interrupt-driven input
- (dispnew.c:3878), OS will send the event to the correct NSWindow, but
- for some reason that window has its first responder set to the NSView
- most recently updated (I guess), which is not the correct one. */
- [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent];
- return;
- }
+ ptrdiff_t start; + ptrdiff_t start;
+ ns_ax_visible_run *runs = NULL; + ns_ax_visible_run *runs = NULL;
+ NSUInteger nruns = 0; + NSUInteger nruns = 0;
+ NSString *text = ns_ax_buffer_text (w, &start, &runs, &nruns); + NSString *text = ns_ax_buffer_text (w, &start, &runs, &nruns);
+
- if (nsEvArray == nil)
- nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1];
+ [cachedText release]; + [cachedText release];
+ cachedText = [text retain]; + cachedText = [text retain];
+ cachedTextModiff = modiff; + cachedTextModiff = modiff;
+ cachedTextStart = start; + cachedTextStart = start;
+
- [NSCursor setHiddenUntilMouseMoves:! NILP (Vmake_pointer_invisible)];
+ if (visibleRuns) + if (visibleRuns)
+ xfree (visibleRuns); + xfree (visibleRuns);
+ visibleRuns = runs; + visibleRuns = runs;
+ visibleRunCount = nruns; + visibleRunCount = nruns;
+} +}
+
- if (!hlinfo->mouse_face_hidden
- && FIXNUMP (Vmouse_highlight)
- && !EQ (emacsframe->tab_bar_window, hlinfo->mouse_face_window))
+/* ---- Index mapping ---- */ +/* ---- Index mapping ---- */
+ +
+/* Convert buffer charpos to accessibility string index. */ +/* Convert buffer charpos to accessibility string index. */
+- (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos +- (NSUInteger)accessibilityIndexForCharpos:(ptrdiff_t)charpos
+{ +{
+ for (NSUInteger i = 0; i < visibleRunCount; i++) + for (NSUInteger i = 0; i < visibleRunCount; i++)
{ + {
- clear_mouse_face (hlinfo);
- hlinfo->mouse_face_hidden = true;
+ ns_ax_visible_run *r = &visibleRuns[i]; + ns_ax_visible_run *r = &visibleRuns[i];
+ if (charpos >= r->charpos && charpos < r->charpos + r->length) + if (charpos >= r->charpos && charpos < r->charpos + r->length)
+ return r->ax_start + (NSUInteger) (charpos - r->charpos); + return r->ax_start + (NSUInteger) (charpos - r->charpos);
@@ -672,17 +497,12 @@ index 932d209..ca6b969 100644
+ map it to the start of the next visible run. */ + map it to the start of the next visible run. */
+ if (charpos < r->charpos) + if (charpos < r->charpos)
+ return r->ax_start; + return r->ax_start;
} + }
-
- if (!processingCompose)
+ /* Past end — return total length. */ + /* Past end — return total length. */
+ if (visibleRunCount > 0) + if (visibleRunCount > 0)
{ + {
- /* FIXME: What should happen for key sequences with more than
- one character? */
- code = ([[theEvent charactersIgnoringModifiers] length] == 0) ?
+ ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1]; + ns_ax_visible_run *last = &visibleRuns[visibleRunCount - 1];
+ return last->ax_start + (NSUInteger) last->length; + return last->ax_start + last->ax_length;
+ } + }
+ return 0; + return 0;
+} +}
@@ -694,7 +514,7 @@ index 932d209..ca6b969 100644
+ { + {
+ ns_ax_visible_run *r = &visibleRuns[i]; + ns_ax_visible_run *r = &visibleRuns[i];
+ if (ax_idx >= r->ax_start + if (ax_idx >= r->ax_start
+ && ax_idx < r->ax_start + (NSUInteger) r->length) + && ax_idx < r->ax_start + r->ax_length)
+ return r->charpos + (ptrdiff_t) (ax_idx - r->ax_start); + return r->charpos + (ptrdiff_t) (ax_idx - r->ax_start);
+ } + }
+ /* Past end — return last charpos. */ + /* Past end — return last charpos. */
@@ -1396,272 +1216,18 @@ index 932d209..ca6b969 100644
+#endif /* NS_IMPL_COCOA */ +#endif /* NS_IMPL_COCOA */
+ +
+ +
+/* ========================================================================== /* ==========================================================================
+
+ EmacsView implementation
+
+ ========================================================================== */
+
+
+@implementation EmacsView
+
+- (void)windowDidEndLiveResize:(NSNotification *)notification
+{
+ [self updateFramePosition];
+}
+
+/* Needed to inform when window closed from lisp. */
+- (void) setWindowClosing: (BOOL)closing
+{
+ NSTRACE ("[EmacsView setWindowClosing:%d]", closing);
+
+ windowClosing = closing;
+}
+
+
+- (void)dealloc
+{
+ NSTRACE ("[EmacsView dealloc]");
+
+ /* Clear the view resize notification. */
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:NSViewFrameDidChangeNotification
+ object:nil];
+
+ if (fs_state == FULLSCREEN_BOTH)
+ [nonfs_window release];
+
+#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
+ /* Release layer and menu */
+ EmacsLayer *layer = (EmacsLayer *)[self layer];
+ [layer release];
+#endif
+
+ [accessibilityElements release];
+ [[self menu] release];
+ [super dealloc];
+}
+
+
+/* Called on font panel selection. */
+- (void) changeFont: (id) sender
+{
+ struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
+ NSFont *nsfont;
+
+#ifdef NS_IMPL_GNUSTEP
+ nsfont = ((struct nsfont_info *) font)->nsfont;
+#else
+ nsfont = (NSFont *) macfont_get_nsctfont (font);
+#endif
+
+ if (!font_panel_active)
+ return;
+
+ if (font_panel_result)
+ [font_panel_result release];
+
+ font_panel_result = (NSFont *) [sender convertFont: nsfont];
+
+ if (font_panel_result)
+ [font_panel_result retain];
+
+#ifndef NS_IMPL_COCOA
+ font_panel_active = NO;
+ [NSApp stop: self];
+#endif
+}
+
+#ifdef NS_IMPL_COCOA
+- (void) noteUserSelectedFont
+{
+ font_panel_active = NO;
+
+ /* If no font was previously selected, use the currently selected
+ font. */
+
+ if (!font_panel_result && FRAME_FONT (emacsframe))
+ {
+ font_panel_result
+ = macfont_get_nsctfont (FRAME_FONT (emacsframe));
+
+ if (font_panel_result)
+ [font_panel_result retain];
+ }
+
+ [NSApp stop: self];
+}
+
+- (void) noteUserCancelledSelection
+{
+ font_panel_active = NO;
+
+ if (font_panel_result)
+ [font_panel_result release];
+ font_panel_result = nil;
+
+ [NSApp stop: self];
+}
+#endif
+
+- (Lisp_Object) showFontPanel
+{
+ id fm = [NSFontManager sharedFontManager];
+ struct font *font = FRAME_OUTPUT_DATA (emacsframe)->font;
+ NSFont *nsfont, *result;
+ struct timespec timeout;
+#ifdef NS_IMPL_COCOA
+ NSView *buttons;
+ BOOL canceled;
+#endif
+
+#ifdef NS_IMPL_GNUSTEP
+ nsfont = ((struct nsfont_info *) font)->nsfont;
+#else
+ nsfont = (NSFont *) macfont_get_nsctfont (font);
+#endif
+
+#ifdef NS_IMPL_COCOA
+ buttons
+ = ns_create_font_panel_buttons (self,
+ @selector (noteUserSelectedFont),
+ @selector (noteUserCancelledSelection));
+ [[fm fontPanel: YES] setAccessoryView: buttons];
+ [buttons release];
+#endif
+
+ [fm setSelectedFont: nsfont isMultiple: NO];
+ [fm orderFrontFontPanel: NSApp];
+
+ font_panel_active = YES;
+ timeout = make_timespec (0, 100000000);
+
+ block_input ();
+ while (font_panel_active
+#ifdef NS_IMPL_COCOA
+ && (canceled = [[fm fontPanel: YES] isVisible])
+#else
+ && [[fm fontPanel: YES] isVisible]
+#endif
+ )
+ ns_select_1 (0, NULL, NULL, NULL, &timeout, NULL, YES);
+ unblock_input ();
+
+ if (font_panel_result)
+ [font_panel_result autorelease];
+
+#ifdef NS_IMPL_COCOA
+ if (!canceled)
+ font_panel_result = nil;
+#endif
+
+ result = font_panel_result;
+ font_panel_result = nil;
+
+ [[fm fontPanel: YES] setIsVisible: NO];
+ font_panel_active = NO;
+
+ if (result)
+ return ns_font_desc_to_font_spec ([result fontDescriptor],
+ result);
+
+ return Qnil;
+}
+
+- (BOOL)acceptsFirstResponder
+{
+ NSTRACE ("[EmacsView acceptsFirstResponder]");
+ return YES;
+}
+
+/* Tell NS we want to accept clicks that activate the window */
+- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
+{
+ NSTRACE_MSG ("First mouse event: type=%ld, clickCount=%ld",
+ [theEvent type], [theEvent clickCount]);
+ return ns_click_through;
+}
+- (void)resetCursorRects
+{
+ NSRect visible = [self visibleRect];
+ NSCursor *currentCursor = FRAME_POINTER_TYPE (emacsframe);
+ 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
+}
+
+
+
+/*****************************************************************************/
+/* Keyboard handling. */
+#define NS_KEYLOG 0
+
+- (void)keyDown: (NSEvent *)theEvent
+{
+ Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (emacsframe);
+ int code;
+ unsigned fnKeysym = 0;
+ static NSMutableArray *nsEvArray;
+ unsigned int flags = [theEvent modifierFlags];
+
+ NSTRACE ("[EmacsView keyDown:]");
+
+ /* Rhapsody and macOS give up and down events for the arrow keys. */
+ if ([theEvent type] != NSEventTypeKeyDown)
+ return;
+
+ if (!emacs_event)
+ return;
+
+ if (![[self window] isKeyWindow]
+ && [[theEvent window] isKindOfClass: [EmacsWindow class]]
+ /* We must avoid an infinite loop here. */
+ && (EmacsView *)[[theEvent window] delegate] != self)
+ {
+ /* XXX: There is an occasional condition in which, when Emacs display
+ updates a different frame from the current one, and temporarily
+ selects it, then processes some interrupt-driven input
+ (dispnew.c:3878), OS will send the event to the correct NSWindow, but
+ for some reason that window has its first responder set to the NSView
+ most recently updated (I guess), which is not the correct one. */
+ [(EmacsView *)[[theEvent window] delegate] keyDown: theEvent];
+ return;
+ }
+
+ if (nsEvArray == nil)
+ nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1];
+
+ [NSCursor setHiddenUntilMouseMoves:! NILP (Vmake_pointer_invisible)];
+
+ if (!hlinfo->mouse_face_hidden
+ && FIXNUMP (Vmouse_highlight)
+ && !EQ (emacsframe->tab_bar_window, hlinfo->mouse_face_window))
+ {
+ clear_mouse_face (hlinfo);
+ hlinfo->mouse_face_hidden = true;
+ }
+
+ if (!processingCompose)
+ {
+ /* FIXME: What should happen for key sequences with more than
+ one character? */
+ code = ([[theEvent charactersIgnoringModifiers] length] == 0) ?
0 : [[theEvent charactersIgnoringModifiers] characterAtIndex: 0];
/* Is it a "function key"? */ EmacsView implementation
@@ -8237,6 +9341,27 @@ ns_in_echo_area (void) @@ -6889,6 +7993,7 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action)
[layer release];
#endif
+ [accessibilityElements release];
[[self menu] release];
[super dealloc];
}
@@ -8237,6 +9342,27 @@ ns_in_echo_area (void)
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
@@ -1689,7 +1255,7 @@ index 932d209..ca6b969 100644
} }
@@ -9474,6 +10599,297 @@ ns_in_echo_area (void) @@ -9474,6 +10600,297 @@ ns_in_echo_area (void)
return fs_state; return fs_state;
} }
@@ -1987,7 +1553,7 @@ index 932d209..ca6b969 100644
@end /* EmacsView */ @end /* EmacsView */
@@ -9941,6 +11357,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c) @@ -9941,6 +11358,14 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
return [super accessibilityAttributeValue:attribute]; return [super accessibilityAttributeValue:attribute];
} }