12

我有一个NSTextView子类作为它的NSTextStorage代表。我正在尝试做两件事:

  1. 以某些方式突出显示文本
  2. 评估文本,然后将答案附加到文本视图。

我在两种不同的方法中执行此操作,均由- (void)textStorageWillProcessEditing:(NSNotification *)notification委托回调调用。

我可以很好地突出显示语法,但是在附加我的答案时,插入点会跳到行尾,我真的不知道为什么。我的评估方法如下所示:

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];

这样做会很好地附加我的结果,但问题是,如前所述,插入点会跳到末尾。

我试过了:

  1. 将上述调用包装在[textStorage beginEditing]和中-endEditing
  2. 在更改文本存储之前保存选择范围(即插入点),以便之后我可以重置它,但没有骰子。

我这样做对吗?我正在尝试以最简单的方式做到这一点,而且我也不确定这是否是进行解析/突出显示的理想场所。文档让我相信这一点,但也许这是错误的。

4

3 回答 3

5

插入点移动的原因

令人惊讶的是,我从来没有找到解释为什么这些建议有效(或无效)的实际解释。

深入研究,插入点移动的原因是:(.editedCharactersNSTextStorageEditedCharactersObjC中)影响插入点的位置NSLayoutManager.processEditing(from:editedMask:...)

如果只发送.editedAttributes/ NSTextStorageEditedAttributes,则不会触摸插入点。这是您在突出显示时想要实现的目标:仅更改属性。

为什么突出显示会影响插入点

此处突出显示的问题在于,它在单个处理运行期间NSTextStorage收集所有调用并组合范围,从用户编辑的更改(例如,键入时的插入)开始,然后形成这个和所有报告的范围的联合。这将导致一个调用 -两个调用中的一个。editedaddAttributes(_:range:)NSLayoutManager.processEditing(from:editedMask:...)editedMask[.editedCharacters, .editedAttributes]

因此,您发送.editedAttributes突出显示的范围,但最终形成了一个联合.editedCharacters。该联合将插入点 waaaaaaaay 移动到它应该去的位置之外。

更改顺序processEditing以调用 super first 有效,因为将通知布局管理器完成编辑。但是这种方法仍然会在某些极端情况下中断,导致在您输入非常大的段落时布局无效或滚动视图抖动。

顺便说一句,这对于挂钩也是如此NSTextStorageDelegate

在布局真正完成后挂钩回调以触发突出显示而不是processEditing

基于 Cocoa 框架固有的原因,唯一能够稳健工作的解决方案是仅执行突出显示textDidChange(_:),即在布局处理真正完成之后。订阅NSTextDidChangeNotification工作也一样。

缺点:您必须触发高亮传递以对底层字符串进行编程更改,因为这些不会调用textDidChange(_:)回调。


如果您想更多地了解问题的根源,我将更多我的研究、不同的方法和解决方案的细节放在更长的博客文章中以供参考。这篇文章本身仍然是一个独立的解决方案:http: //christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/

于 2017-11-30T08:56:14.987 回答
4

我知道这个问题早已得到回答,但是我遇到了完全相同的问题。在我的NSTextStorage子类中,我正在执行以下操作:

- (void)processEditing {
    //Process self.editedRange and apply styles first
    [super processEditing];
}

但是,正确的做法是:

- (void)processEditing {
    [super processEditing];
    //Process self.editedRange and apply styles after calling superclass method
}
于 2014-03-23T20:16:21.810 回答
0

这很简单!我最终把这个问题分成两部分。由于textStorage委托回调,我仍然会突出显示语法,但现在我会进行评估并附加到其他地方。

我最终都覆盖了-insertText:and -deleteBackwards:(我可能也想对 做同样的-deleteForwards:事情)。两个覆盖都如下所示:

- (void)insertText:(id)insertString {
    [super insertText:insertString];
    NSRange selectedRange = [self selectedRange];
    [self doEvaluationAndAppendResult];
    [self setSelectedRange:selectedRange];
}

我最终不得不在这里手动重置插入点。我仍然很想弄清楚为什么这是必要的,但至少这感觉不像是黑客攻击。

于 2012-08-28T12:45:51.090 回答