NSTextView.menuForEvent - getting the affected range on macOS 27

I have an NSTextView subclass that replaces the standard context menu with a custom one by building its own menu in an menuForEvent: override. The context menu that is shown depends on what is selected in the text view, so it checks the current selectedRange to determine which commands should be included.

(I could do much the same via the NSTextViewDelegate method textView(_:menu:for:at:), but since my text view subclass is used in several areas, each with their own delegate, it's better to do it in menuForEvent(_:).)

This has all worked fine for years, but with NSTextView's transition to gesture recognisers and NSTextSelectionManager for handling text selections in macOS 27, the timings have changed. Previously, if you Ctrl-clicked on a word so that the word is selected and the context menu appears, setSelectedRanges:affinity:stillSelecting: would be called first and then menuForEvent: would be called. On macOS 27, however, the order of events is reversed: menuForEvent: is called first, before setSelectedRanges:affinity:stillSelecting:. (If you implement NSMenuDelegate.menuNeedsUpdate:, that is also called before setSelectedRanges:....)

This means that you can no longer rely on selectedRanges in menuForEvent:, because on macOS 27, selectedRanges in menuForEvent: represents the previous selection, not the selection you will actually see on screen when the menu appears. This makes building a custom context menu somewhat tricky.

I suspect this is a bug and have reported it as such (FB23251873, with apologies to the engineers for the pleading tone in that report; it was at the end of a long week of getting my head around the gesture recogniser changes. :) ). But it occurs to me that there is nothing in the documentation that guarantees that selectedRanges will be accurate in menuForEvent:; it's just always worked this way.

So am I missing something here? Is there a better or more reliable way of getting the range that will be affected by the contextual menu in NSTextView? (The delegate method only provides a single character index, which isn't enough information for spelling corrections and such which require a range.)

My current workaround is to rebuild the context menu in setSelectedRanges:affinity:stillSelecting: if it is called between menuForEvent: and didCloseMenu:withEvent:.

Thanks for filing the Feedback. You are correct that there is no guarantee for when the selection is actually changed but this is long standing behavior. Good news is that I saw your bug come by and just posted a fix.

NSTextView.menuForEvent - getting the affected range on macOS 27
 
 
Q