自定义NSTextView insertText:replacementRange中断拼写检查

时间:2017-08-16 05:02:32

标签: objective-c macos cocoa spell-checking nstextview

我有一个自定义的NSTextView子类,还有一个自定义的NSTextStorage组件。 NSTextStorage根据上下文修改用户输入的文本。

因为最终文本可能比用户最初输入的文本短,所以我必须在我的NSTextView中覆盖insertText:replacementRange。最小的例子是:

- (void) insertText:(id)string replacementRange:(NSRange)replacementRange {
    if ([self hasMarkedText]) {
        [[self textStorage] replaceCharactersInRange:[self markedRange] withString:string];
    } else {
        [[self textStorage] replaceCharactersInRange:[self selectedRange] withString:string];
    }

    [self didChangeText];
}

这可以在几个月的广泛测试中正常工作....除非禁用自动拼写检查和纠正。 “squigglies”不会出现拼写错误的单词,除非我停止输入,移动鼠标,并将焦点切换到我的应用程序。几秒钟后,整个textview都会拼写检查。因为它发生在事后,当然会禁用自动校正。

如果我禁用自定义insertText:replacementRange:方法,其他所有方法都可以正常运行,并且自动拼写功能会返回。我只需要注意不要触发导致缩短文本的更改,因为它会触发属性超出范围错误(首先是我自定义方法的原因。)

显然,Apple对insertText:replacementRange:的实施比我的更多。我在[self checkTextInRange...][self checkTextInSelection:]等上尝试了多种变体。它们都没有恢复正常的功能。

搜索Apple的文档并没有帮助我指出我从我的方法中遗漏的导致拼写检查中断的内容。任何指针或想法都会非常感激!!

提前致谢!

编辑:以下是我的NSTextStorage提供的各种行为的一些示例。 (|代表插入符号)

从:

开始
* item
* |

如果我点击返回键,我最终得到以下内容(删除*<space>):

* item
|

另一个例子,如果启用了“更改跟踪”:

this is thee| time

如果我点击删除:

this is the|{--e--} time

如您所见,单次击键可能会导致在文本中添加或删除多个字符。

编辑2 :仅供参考 - 当文件末尾按下返回时缩短发生时,我发现属性超出范围的问题 - NSTextview尝试设置新的段落样式才发现文件比预期的要短。我无法改变NSTextview目标的范围。

2 个答案:

答案 0 :(得分:0)

我有部分解决方案。

在我的自定义insertText:replacementRange:方法中,didChangeText之前:

NSinteger wordCount;
NSOrthography * orthography;

static NSInteger theWordCount;
NSOrthography  * orthography;

NSRange spellingRange = <range to check>

NSArray * results = [[NSSpellChecker sharedSpellChecker] checkString:[[self textStorage] string]
                                                               range:spellingRange
                                                               types:[self enabledTextCheckingTypes]
                                                             options:NULL
                                              inSpellDocumentWithTag:0
                                                         orthography:&orthography
                                                           wordCount:&theWordCount];
if (results.count) {
    [self handleTextCheckingResults:results forRange:spellingRange types:[self enabledTextCheckingTypes] options:@{} orthography:orthography wordCount:theWordCount];
}

然而,这是不完整的:

  • 拼写检查和语法检查正常
  • 自动拼写纠正和文字替换不起作用(即使已启用)

答案 1 :(得分:0)

(EDITED 2018-05-30)

更新回复(2018-05-22):

这个问题又重新抬头,我真的需要弄清楚。

  1. 我的自定义NSTextStorage与描述基本相同,但仍有效。

  2. 我在我的NSTextView上使用自定义insertText:replacementRange:,但它调用[super insertText:replacementRange:]来利用Apple的幕后工作,使拼写等工作得更好。我的自定义方法只需要设置一个布尔值。

  3. 缩短文本时,我仍然会收到来自Apple insertText:replacementRange:的请求,要求提供文本中不存在的部分中的属性。以前,我会被困在这里,因为我尝试的一切都导致了崩溃,或导致Apple的代码无限期地重复请求不存在的属性。

  4. 最后,我尝试使用NULL范围指针返回虚假属性,这似乎让Apple的代码感到高兴:

    - (NSDictionary *) attributesAtIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)range {
        if (location > _backingAttributes.length) {
            // This happens if we shrink the text before the textview is aware of it.
            // For example, if we expand "longtext" -> "short" in our smart string, then
            // The textview may set and request attributes past the end of our
            // _backing string.
            // Initially this was due to error in my code, but now I had to add
            // This error checking back
            NSLog(@"get attributes at (%lu) in (%lu)", (unsigned long)location, (unsigned long)_backingAttributes.length);
            NSLog(@"error");
    
            // Apparently returning fake attributes satisfies [NSTextView insertText:replacementRange:]
            range = NULL;
            return  @{
                      NSForegroundColorAttributeName : [BIColor redColor],
                      NSFontAttributeName : [BIFont fontWithName:@"Helvetica" size:14.0]
                      };
    
        } else {
            return [_backingAttributes attributesAtIndex:location effectiveRange:range];
        }
    }
    
  5. 经过进一步测试,结果证明还不够。我最后将以下内容添加到setter中,以存储macOS尝试设置的无效属性和范围:

    - (void) setAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range {
        if (NSMaxRange(range) > _backingAttributes.length) {
            _invalidAttrs = attrs;
            _invalidRange = range;
        } else {
            [self beginEditing];
            [_backingAttributes setAttributes:attrs range:range];
            [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
            [self endEditing];
        }
    }
    
  6. 我更新了`attributesAtIndex:effectiveRange:在使用无效范围调用时返回以下内容,而不是返回上面的假属性:

    // Apparently returning fake attributes satisfies [NSTextView insertText]
    *range = _invalidRange;
    return _invalidAttrs;
    
  7. 这似乎可以在以前触发异常或无限循环的各种条件下工作。