NSTextView ReplaceCharactersInRange:withString: 对应 shouldChangeTextInRanges:replacementStrings:

发布于 2024-10-31 00:27:20 字数 547 浏览 3 评论 0原文

我的一个项目中有一个相当复杂的 NSTextView 子类。我目前正在努力让查找/替换与内联查找栏(例如 Safari、Xcode)一起使用,并希望正确支持替换操作的撤消/重做。

我希望“全部替换”命令支持将撤消作为单个命令(即,如果在文本视图中要进行 8 个替换,它也应该立即撤消这 8 个替换)。

我想知道是否有与 shouldChangeTextInRanges:replaceStrings: 相对应的内容,我可以在检查后调用它来进行替换。我预计会有一个 replaceCharactersInRanges:withStrings: 或类似的东西,但似乎没有。

目前我能想到的唯一方法是首先检查对 shouldChangeTextInRanges:replaceStrings: 的调用,然后使用整个范围调用 replaceCharactersInRange:withString:文本视图和新字符串(已进行替换)作为第二个参数。

这似乎没有必要,如果不需要的话,我真的不想替换整个字符串。有什么想法吗?

I have a fairly complicated NSTextView subclass in one of my projects. I'm currently working on getting find/replace to work with an inline find bar (e.g. Safari, Xcode) and want to properly support undo/redo for the replace operations.

I want the Replace All command to support undo as a single command (i.e. if there are 8 replacements to be made in the text view, it should also undo those 8 replacements at once).

I'm wondering if there is a counterpart to shouldChangeTextInRanges:replaceStrings: that I can call after checking to do the replacement. I expected there would be a replaceCharactersInRanges:withStrings: or something similar, but there doesn't seem to be.

The only way I can think to do this at the moment is to check with a call to shouldChangeTextInRanges:replaceStrings: first, then call replaceCharactersInRange:withString: with the entire range of the text view and the new string (with the replacements made) as the second argument.

This just seems unnecessary, I don't really want to replace the entire string if I don't have to. Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

缺⑴份安定 2024-11-07 00:27:20

经过一番修补后,我想我已经弄清楚了。乔希,我按照你的建议开始了。我不确定您是否编辑了您的建议或只是删除了它,但它已经消失了,所以我无法在答案中引用它。

不管怎样,你必须在每次调用replaceCharactersInRange:withString:之后改变你要替换的范围,否则会因为范围不匹配而发生不好的事情。这就是我最终得到的结果:

// array of NSValue objects storing an NSRange
NSArray *replaceRanges = [self replaceRanges];
NSString *replaceString = [self replaceString];
// array of NSString objects you are going to use for the replace operation, just replaceString repeated [replaceRanges count] times
NSArray *replaceStrings = [self replaceStrings];
NSTextView *textView = [self textView];
// the amount we have to shift subequent replace ranges after each call to replaceCharactersInRange:withString:
NSInteger locationShift = 0;

// check to makes sure the replace can occur
if ([textView shouldChangeTextInRanges:replaceRanges replacementStrings:replaceStrings]) {
    // we want to treat all these replacements as a single replacement for undo purposes
    [[textView textStorage] beginEditing];

    for (NSValue *rangeValue in replaceRanges) {
        NSRange range = [rangeValue rangeValue];

        // replace the range shifted by locationShift with our replaceString
        [[textView textStorage] replaceCharactersInRange:NSMakeRange(range.location + locationShift, range.length) withString:replaceString];

        // update the shift amount, which is the difference between our replaced string length and the original match length
        locationShift += [replaceString length] - range.length;
    }
    // end the grouping operation
    [[textView textStorage] endEditing];
}

这非常有效并且支持按预期撤消,撤消此操作会立即撤消所有替换。

不管怎样,感谢乔什,因为他的回答让我指明了正确的方向。

After some tinkering I think I've got this figured out. Josh, I used your suggestion to get started. I'm not sure if you edited your suggestion or just deleted it, but it's gone so I can't quote it in my answer.

Anyways, you have to shift the ranges you are going to replace after each invocation of replaceCharactersInRange:withString: or else bad things happen as the ranges don't match up. Here is what I ended up with:

// array of NSValue objects storing an NSRange
NSArray *replaceRanges = [self replaceRanges];
NSString *replaceString = [self replaceString];
// array of NSString objects you are going to use for the replace operation, just replaceString repeated [replaceRanges count] times
NSArray *replaceStrings = [self replaceStrings];
NSTextView *textView = [self textView];
// the amount we have to shift subequent replace ranges after each call to replaceCharactersInRange:withString:
NSInteger locationShift = 0;

// check to makes sure the replace can occur
if ([textView shouldChangeTextInRanges:replaceRanges replacementStrings:replaceStrings]) {
    // we want to treat all these replacements as a single replacement for undo purposes
    [[textView textStorage] beginEditing];

    for (NSValue *rangeValue in replaceRanges) {
        NSRange range = [rangeValue rangeValue];

        // replace the range shifted by locationShift with our replaceString
        [[textView textStorage] replaceCharactersInRange:NSMakeRange(range.location + locationShift, range.length) withString:replaceString];

        // update the shift amount, which is the difference between our replaced string length and the original match length
        locationShift += [replaceString length] - range.length;
    }
    // end the grouping operation
    [[textView textStorage] endEditing];
}

This works great and supports undo as expected, undoing this operation results in all the replacements being undone at once.

Anyways thanks to Josh, as his answer got me pointed in the right direction.

王权女流氓 2024-11-07 00:27:20

令我惊讶的是,撤消操作并未自动分组。但是,您可以手动进行撤消分组;您必须自己设置相反的操作。希望这将为您指明正确的方向:

- (BOOL)shouldChangeTextInRanges:(NSArray *)affectedRanges replacementStrings:(NSArray *)replacementStrings {

    NSUndoManager * undoMan = [self undoManager];
    [undoMan beginUndoGrouping];
    NSEnumerator stringEnumerator = [replacementStrings objectEnumerator];
    for( NSRange thisRange in affectedRanges ){
        NSString * thisString = [stringEnumerator nextObject];
        NSTextStorage * textStore = [self textStorage];
        [[undoMan prepareWithInvocationTarget:textStore] 
                 replaceCharactersInRange:thisRange 
                               withString:[textStore attributedSubstringFromRange:thisRange]];

        [textStore replaceCharactersInRange:thisRange withString:thisString];
    }
    [undoMan endUndoGrouping];
    [undoMan setActionName:@"Replace All"];

    return NO; // because we just did it by hand
}

I'm surprised the undo for that isn't grouped automatically. However, you can do manual undo grouping; you'll have to set up the inverse actions yourself. Hopefully this will point you in the right direction:

- (BOOL)shouldChangeTextInRanges:(NSArray *)affectedRanges replacementStrings:(NSArray *)replacementStrings {

    NSUndoManager * undoMan = [self undoManager];
    [undoMan beginUndoGrouping];
    NSEnumerator stringEnumerator = [replacementStrings objectEnumerator];
    for( NSRange thisRange in affectedRanges ){
        NSString * thisString = [stringEnumerator nextObject];
        NSTextStorage * textStore = [self textStorage];
        [[undoMan prepareWithInvocationTarget:textStore] 
                 replaceCharactersInRange:thisRange 
                               withString:[textStore attributedSubstringFromRange:thisRange]];

        [textStore replaceCharactersInRange:thisRange withString:thisString];
    }
    [undoMan endUndoGrouping];
    [undoMan setActionName:@"Replace All"];

    return NO; // because we just did it by hand
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文