问题
I'm trying to implement a syntax-coloring text editor that also does things like insert whitespace at the start of a new line for you, or replace text with text attachments.
After perusing the docs again after a previous implementation had issues with undoing, it seems like the recommended bottleneck for this is NSTextStorageDelegate's textStorage(_,willProcessEditing:,range:,changeInLength:) method (which states that Delegates can change the characters or attributes.
, whereas didProcessEditing
says I can only change attributes). This works fine, except that whenever I actually change attributes or text, the text insertion mark moves to the end of whatever range of text I modify (so if I change the style of the entire line, the cursor goes at the end of the line).
Does anybody know what additional call(s) I am missing that tell NSTextStorage/NSTextView not to screw up the insertion mark? Also, once I insert text, I might have to tell it to move the insertion mark to account for text I've inserted.
Note: I've seen Modifying NSTextStorage causes insertion point to move to the end of the line, but that assumes I'm subclassing NSTextStorage, so I can't use the solution there (and would rather not subclass NSTextStorage, as it's a semi-abstract subclass and I'd lose certain behaviours of Apple's class if I subclassed it).
回答1:
I found out the source of the problem.
And the only solution that will work robustly based on reasons inherent to the Cocoa framework instead of mere work-arounds. (Note there's probably at least one other, metastable approach based on a ton of quick-fixes that produces a similar result, but as metastable alternatives go, that'll be very fragile and require a ton of effort to maintain.)
TL;DR Problem:
NSTextStorage
collectsedited
calls and combines the ranges, starting with the user-edited change (e.g. the insertion), then adding all ranges fromaddAttributes(_:range:)
calls during highlighting.TL;DR Solution: Perform highlighting from
textDidChange(_:)
exclusively.
Details
This only applies to a single processEditing()
run, both in NSTextStorage
subclasses and in NSTextStorageDelegate
callbacks.
The only safe way to perform highlighting I found is to hook into NSText.didChangeNotification
or implement NSTextDelegate.textDidChange(_:)
.
As per @Willeke's comments to the OP's question, this is the best place to perform changes after the layout pass. But as opposed to the comment thread, setting back NSText.selectedRange
does not suffice. You won't notice the problem of post-fixing the selection after the caret has moved away until
- you highlight whole blocks of text,
- spanning multiple lines, and
- exceeding the visible (
NSClipView
) boundaries of the scroll view.
In this rare case, most keystrokes will make the scroll view jiggle or bounce around. But there's no additional quick-fix against this. I tried. Neither preventing sending the scroll commands from private API in NSLayoutManager
nor avoiding scrolling by overriding all methods with "scroll" in them from a NSTextView
subclass works well. You can stop scrolling to the insertion point altogether, sure, but no such luck getting a solid algorithm out that does not scroll only when you perform highlighting.
The didChangeNotification
approach does work reliably in all situations I and my app's testers were able to come up with (including a crash situation as weird as scrolling the text and then, during the animation, replacing the string with something shorter -- yeah, try to figure that kind of stuff out from crash logs that report invalid glyph generation ...).
This approach works because it does 2 glyph generation passes:
- One pass for the edited range, in the case of typing for every key stroke with a
NSRange
of length 1, sending theedited
notification with both[.editedCharacters, .editedAttributes]
, the former being responsible for moving the caret; - another pass for whatever range is affected by syntax highlighting, sending the
edited
notification with[.editedAttributes]
only, thus not affecting the caret's position at all.
Even more details
In case you want to know more about the source of the problem, I put more my research, different approaches, and details of the solution in a much longer blog post for reference. This here, though, is the solution itself. http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/
来源:https://stackoverflow.com/questions/45126948/nstextstoragedelegates-textstorage-willprocessediting-range-changeinlength