问题描述
我有一个NSTextView
子类作为其NSTextStorage
委托.我正在尝试做两件事:
I've got an NSTextView
subclass acting as its NSTextStorage
delegate. I'm trying to do 2 things:
- 以某些方式突出显示文本
- 评估文本,然后将答案附加到textview.
我正在通过两种不同的方法执行此操作,这两种方法均由- (void)textStorageWillProcessEditing:(NSNotification *)notification
委托回调调用.
I'm doing this in two different methods, both invoked by the - (void)textStorageWillProcessEditing:(NSNotification *)notification
delegate callback.
我可以很好地突出显示语法,但是在添加答案时,插入点跳到行尾,我真的不知道为什么.我的评估方法如下所示:
I can do the syntax highlighting just fine, but when it comes to appending my answer, the insertion point jumps to the end of the line and I don't really know why. My evaluate method looks like the following:
NSString *result = ..;
NSRange lineRange = [[textStorage string] lineRangeForRange:[self selectedRange]];
NSString *line = [[textStorage string] substringWithRange:lineRange];
line = [self appendResult:result toLine:line]; // appends the answer
[textStorage replaceCharactersInRange:lineRange withString:line];
这样做会很好地附加我的结果,但是如上所述,问题是插入点跳到了最后.
Doing that will append my result just fine, but the problem is, as mentioned, the insertion point jumps to the end.
我尝试过:
- 将上述内容包装在
[textStorage beginEditing]
和-endEditing
中. - 在更改文本存储之前保存选择范围(即插入点),以便以后可以重置它,但没有骰子.
- Wrapping those above calls up in
[textStorage beginEditing]
and-endEditing
. - Saving the selection range (i.e., the insertion point) before changing the text storage so I can reset it afterwards, but no dice.
我这样做正确吗?我正在尝试以最少的方式进行此操作,并且我也不确定这是否是进行解析/突出显示的理想位置.文档使我相信了这一点,但也许是错误的.
Am I doing this right? I'm trying to do this the least hackish way, and I'm also unsure if this is the ideal place to be doing my parsing/highlighting. The docs lead me to believe this, but maybe it's wrong.
推荐答案
插入点移动的原因
令人惊讶的是,我从未找到关于这些建议为何起作用(或不起作用)的实际解释.
Reason for the insertion point to move
Suprisingly, I never found an actual explanation to why these suggestion do (or do not) work.
深入其中,使插入点移动的原因为:.editedCharacters
(在ObjC中为NSTextStorageEditedCharacters
)会影响NSLayoutManager.processEditing(from:editedMask:...)
中插入点的位置.
Digging into it, the reason for the insertion point to move is: .editedCharacters
(NSTextStorageEditedCharacters
in ObjC)affects the position of the insertion point from NSLayoutManager.processEditing(from:editedMask:...)
.
如果仅发送.editedAttributes
/NSTextStorageEditedAttributes
,将不会触摸插入点.这是您突出显示时要实现的:仅更改属性.
If only .editedAttributes
/NSTextStorageEditedAttributes
is sent, the insertion point will not be touched. This is what you will want to achieve when you highlight: change attributes only.
突出显示问题是,NSTextStorage
在单个处理运行期间收集所有edited
调用并合并范围,从用户编辑的更改开始(例如,键入时插入) ,然后形成该值与addAttributes(_:range:)
报告的所有范围的并集.这样会导致一个NSLayoutManager.processEditing(from:editedMask:...)
调用-两个[.editedCharacters, .editedAttributes]
的editedMask
.
The problem with highlighting here is that NSTextStorage
collects all edited
calls during a single processing run and combines the ranges, starting with the user-edited change (e.g. the insertion when typing), then forming a union of this and all ranges reported by addAttributes(_:range:)
. This results in one single NSLayoutManager.processEditing(from:editedMask:...)
call -- with an editedMask
of both [.editedCharacters, .editedAttributes]
.
因此,您想要向突出显示的范围发送.editedAttributes
,但最终与.editedCharacters
组成了并集.该联合将插入点waaaaaaaay移至其应到达的位置之外.
So you want to send .editedAttributes
for the highlighted ranges but end up forming a union with .editedCharacters
instead. That union moves the insertion point waaaaaaaay beyond where it should go.
在processEditing
中更改顺序以调用超级优先操作,因为布局编辑器将收到完成的编辑通知.但是这种方法在某些情况下仍然会失效,导致在您键入非常大的段落时导致无效的布局或滚动的滚动视图.
Changing the order in processEditing
to call super first works because the layout manager will be notified of a finished edit. But this approach will still break for some edge cases, resulting in invalid layout or jiggling scroll views while you type in very large paragraphs.
顺便提一下,这也确实适用于NSTextStorageDelegate
基于Cocoa框架固有的原因,唯一能够稳定运行的解决方案是专门从textDidChange(_:)
中进行突出显示,即在布局处理真正完成之后.订阅NSTextDidChangeNotification
也可以.
The only solution that will work robustly based on reasons inherent to the Cocoa framework is to perform highlighting from textDidChange(_:)
exclusively, i.e. after the layout processing really has been finished. Subscribing to NSTextDidChangeNotification
work just as well.
缺点:您必须触发对基础字符串进行编程更改的高亮显示通道,因为这些更改不会调用textDidChange(_:)
回调.
Downside: you have to trigger highlighting passes for programmatic changes to the underlying string as these will not invoke the textDidChange(_:)
callback.
如果您想更多地了解问题的根源,我将在更长的博客文章中投入更多的研究,不同的方法以及解决方案的详细信息,以供参考.该帖子本身仍然是一个独立的解决方案: http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/
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 post is still a self-contained solution in itself: http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/
这篇关于修改NSTextStorage会导致插入点移动到行尾的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!