我找到了问题的根源。
并且基于 Cocoa 框架固有的原因,而不是单纯的变通方法,唯一能够稳健工作的解决方案。(请注意,可能至少还有另一种基于大量快速修复的亚稳态方法会产生类似的结果,但随着亚稳态替代方案的发展,这将非常脆弱,需要大量的努力来维护。)
细节

这仅适用于单次processEditing()
运行,无论是在NSTextStorage
子类中还是在NSTextStorageDelegate
回调中。
我发现执行突出显示的唯一安全方法是挂钩NSText.didChangeNotification
或实施NSTextDelegate.textDidChange(_:)
.
根据@Willeke 对 OP 问题的评论,这是布局通过后执行更改的最佳位置。但与评论线程相反,回退NSText.selectedRange
是不够的。在插入符号移开后,您不会注意到后修复选择的问题,直到
- 你突出显示整个文本块,
- 跨越多行,和
- 超出滚动视图的可见 (
NSClipView
) 边界。
在这种罕见的情况下,大多数击键都会使滚动视图抖动或弹跳。但是没有针对此的额外快速修复。我试过了。既不能阻止从私有 API 发送滚动命令,NSLayoutManager
也不能通过覆盖子类中带有“滚动”的所有方法来避免滚动NSTextView
。当然,您可以完全停止滚动到插入点,但是没有这样的运气得到一个仅在执行突出显示时不会滚动的可靠算法。
该didChangeNotification
方法在我和我的应用程序的测试人员能够提出的所有情况下都可靠地工作(包括像滚动文本那样奇怪的崩溃情况,然后在动画期间用更短的字符串替换字符串 - 是的,试着弄清楚从报告无效字形生成的崩溃日志中出现的那种东西......)。
这种方法之所以有效,是因为它执行了 2 次字形生成通道:
NSRange
一次通过编辑范围,在每次键入长度为 1的击键的情况下,发送edited
通知两者[.editedCharacters, .editedAttributes]
,前者负责移动插入符号;
- 对受语法高亮影响的任何范围的另一次传递,仅发送
edited
通知[.editedAttributes]
,因此根本不影响插入符号的位置。
更多细节
如果您想更多地了解问题的根源,我将更多我的研究、不同的方法和解决方案的细节放在更长的博客文章中以供参考。不过,这就是解决方案本身。 http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/