I figured out the source of the problem.
And the only solution that will work reliably based on reasons inherent in the Cocoa framework, and not just workarounds. (Note that there is probably at least one more metastable approach based on a ton of quick fixes that gives a similar result, but as the metastable alternatives go, it will be very fragile and require a lot of effort to maintain .)
TL; DR Problem: NSTextStorage collects edited calls and merges ranges, starting with user-modified changes (such as inserts), and then adding all ranges from addAttributes(_:range:) calls during allocation.
TL; DR Solution: select only with textDidChange(_:) .
More details

This applies only to one run of processEditing() , both in subclasses of NSTextStorage and in NSTextStorageDelegate callbacks.
The only safe way to do the selection I found is to connect to NSText.didChangeNotification or implement NSTextDelegate.textDidChange(_:) .
According to @Willeke's comments on the OP question, this is the best place to make changes after going through the layout. But unlike the comment stream, setting NSText.selectedRange not enough. You will not notice a problem after fixing the selection after the carriage has left, until
- you select entire blocks of text,
- spanning multiple lines, and
NSClipView visible ( NSClipView ) scroll borders.
In this rare case, most keystrokes will make the view scroll, swaying or not bouncing. But there are no additional quick fixes. I've tried. Not to prevent scrolling commands from being sent from a private API to NSLayoutManager and to avoid scrolling by overriding all methods by scrolling into them from a subclass of NSTextView works well. You can completely stop scrolling to the insertion point, but you will not be able to get a robust algorithm that does not scroll only when performing the selection.
The didChangeNotification approach works reliably in all situations in which my application testers and I could come up with (including a crash situation, as strange as scrolling text, and then during the animation, replacing the line with something shorter - - Yes, try to understand what material is from crash logs that report incorrect character generation ...).
This approach works because it performs 2 character generations:
- One pass for the edited range, in the case of entering for each keystroke with an
NSRange length 1, sending an edited notification using [.editedCharacters, .editedAttributes] , the first of which is responsible for moving the carriage; - another pass for any range affects syntax highlighting by sending an
edited notification only with [.editedAttributes] , without affecting the caret position at all.
More detailed information
If you want to know more about the source of the problem, I will provide more of my research, various approaches, and solution details in a much longer blog for reference. This, however, is the solution itself. http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/