NSTextView Weird Selection Behavior with NSTextAttachmentCells

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.

It turns out I made a silly mistake. A couple weeks ago I was experimenting a bit and set fieldEditor property to YES. The behavior is caused by fieldEditor being YES and specifying a textContainerInset.

I don't have any reason to have fieldEditor set to YES so just removing that line fixes the issue.

Thanks again for taking the time to reply!

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.

Thanks a lot for the detailed reply.

Adding -ensureLayoutForTextContainer: call in -mouseDown: before calling super does not make the problem go away.

I do have a somewhat complicated subclass and I override cursorUpdate: to make sure the IBeam cursor isn't showing when the mouse hovers at certain points around my token cells. But I commented all that out and I still get the selection behavior issue.

I commented out my other complex customizations but the issue persists.

I am insetting the textContainer a bit to make room for a floating subview I add to the scroll view and wonder if that is somehow related.

So it does appear to be related to setting the textContainerInset.

If I take that out haven't been able to reproduce the weird one click selects all issue. I do need to add a bit of inset so typed text aligns properly for this UI.

I moved the text view's scroll view to a container view so I can add the other view that was previously a floating subview to the container next to it (so I no longer need to set textContainerInset.width) but I still need to adjust textContainerInset.height a tiny bit to get proper appearance.

But as soon as I set textContainerInset (just with a little height and no width this time) it all falls apart again.

Accepted Answer

It turns out I made a silly mistake. A couple weeks ago I was experimenting a bit and set fieldEditor property to YES. The behavior is caused by fieldEditor being YES and specifying a textContainerInset.

I don't have any reason to have fieldEditor set to YES so just removing that line fixes the issue.

Thanks again for taking the time to reply!

NSTextView Weird Selection Behavior with NSTextAttachmentCells
 
 
Q