I have an NSTextView that displays several NSTextAttachmentCells. I notice this weird behavior.
Sometimes if I just click in an empty area of the text view the entire text content selects.
So I implemented the delegate method to catch it:
- (NSRange)textView:(NSTextView *)textView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange
{
if (newSelectedCharRange.length > 1
&& newSelectedCharRange.length > oldSelectedCharRange.length)
{
NSEvent *currentEvent = NSApp.currentEvent;
NSLog(@"Selection expanded from %@ to %@. Event type: %ld, click count: %ld, modifier flags: %lu, current selected ranges: %@",
NSStringFromRange(oldSelectedCharRange),
NSStringFromRange(newSelectedCharRange),
(long)currentEvent.type,
(long)currentEvent.clickCount,
(unsigned long)currentEvent.modifierFlags,
self.selectedRanges);
// put a break point here.
}
return newSelectedCharRange;
}
And I reproduced the issue and this logs out:
Selection expanded from {0, 0} to {0, 6}. Event type: 2, click count: 1, modifier flags: 0, current selected ranges: ( "NSRange: {0, 6}
Click count is only 1 so I didn't accidentally triple click. I know on Golden Gate use of NSEvent.currentEvent isn't the way (but I'm not there yet). A simple workaround would be to block the selection right here in the delegate method when clickCount != 3 (but again I know NSEvent.currentEvent in Golden Gate won't be reliable).
Anyone run into this and have any ideas? It seems to happen after I did a triple click in the text view at some point previously (but not this click). So I got the feeling that maybe the text view isn't resetting some private properties and is treating this single click as a triple click. But I really don't know.
Edit: Hmm maybe it has nothing to do with a previous triple click. May have to do with text selection not accounting for the geometry of the NSTextAttachmentCells. Not sure. But I still have to figure out a way to workaround this because a random select all is really annoying!
Call stack looks like:
** -[MyTextView textView:willChangeSelectionFromCharacterRange:toCharacterRange:] at MyTextView.m
-[NSTextView(NSSharing) setSelectedRanges:affinity:stillSelecting:] ()
-[MyTextView setSelectedRanges:affinity:stillSelecting:] MyTextView.m
+[NSInputAnalytics(TrackedActionsManager) allowActionTrackingAnalyticsWithName:forAction:] () n -[NSTextView mouseDown:] ()
-[MyTextView mouseDown:] **
If you're wondering what my -mouseDown: override does it just calls super. I realize this is not a whole lot to go on but any help would be appreciated.
mouseDown: derives the granularity from clickCount fresh on every click, and a single click with character granularity always proposes a zero-length range, so nothing can widen it to {0, 6}.
Two details in your log tell are interesting. Event type 2 is NSEventTypeLeftMouseUp, not mouse-down and since mouseDown: runs its own tracking loop what I think you're seeing the final selection commit after mouse-up. Since oldSelectedCharRange is {0,0}, which means the mouse-down anchor resolved to character index 0 (otherwise you'd see {6,0} here). The loop selects anchor → current index, and forces a zero-length range when the two are equal. So the view resolved index 0 at mouse-down and index 6 at mouse-up for a click you didn't move, implying that it thinks you dragged from the start of the document to the end.
My suspicion is that layout is happening during the tracking loop. Potentially an attachment cell sizes resolving lazily (async image decode), or a cell that measures from state that changes, so the down hit-test sees incomplete layout and the up hit-test sees final layout. Anything that mutates the text storage while the loop is blocked would do it too.
Here's a quick test assuming you're using TextKit1 (TextKit2 has different mechanics for layout). In your mouseDown: override, before calling super:
[self.layoutManager ensureLayoutForTextContainer:self.textContainer];
If that makes it go away, it's the lazy layout, and the fix is in the cells' geometry (cellSize, cellFrameForTextContainer:proposedLineFragment:glyphPosition:characterIndex:, cellBaselineOffset need to be stable and mutually consistent) rather than in the selection delegate.
Also worth logging [lm characterIndexForPoint:...] and [lm usedRectForTextContainer:] at mouse-down. If an empty-area click reports index 0, that confirms it. Please file a Feedback with a sample project if you can as the down-vs-up asymmetry for a stationary click is the interesting part.