NSTextView replaceCharactersInRange: withString: аналог mustChangeTextInRanges: replaceStrings: - PullRequest
4 голосов
/ 07 апреля 2011

У меня довольно сложный подкласс NSTextView в одном из моих проектов.В настоящее время я работаю над поиском / заменой для работы со встроенной панелью поиска (например, Safari, XCode) и хочу надлежащим образом отменить / повторить операции замены.

Я хочу, чтобы команда Replace All поддерживалаотменить как одну команду (т. е. если в текстовом представлении нужно сделать 8 замен, нужно также отменить эти 8 замен одновременно).

Мне интересно, есть ли аналог shouldChangeTextInRanges:replaceStrings:что я могу позвонить после проверки, чтобы сделать замену.Я ожидал, что будет replaceCharactersInRanges:withStrings: или что-то подобное, но, похоже, его нет.

Единственный способ, которым я могу думать, чтобы сделать это в данный момент, это проверить с помощью вызова shouldChangeTextInRanges:replaceStrings:сначала вызовите replaceCharactersInRange:withString: со всем диапазоном текстового представления и новой строкой (с выполненными заменами) в качестве второго аргумента.

Это просто кажется ненужным, я не хочу заменятьвсю строку, если мне не нужно.Есть идеи?

Ответы [ 2 ]

4 голосов
/ 07 апреля 2011

После некоторой переделки я думаю, что понял это.Джош, я воспользовался твоим предложением, чтобы начать.Я не уверен, что вы отредактировали свое предложение или просто удалили его, но оно пропало, поэтому я не могу процитировать его в своем ответе.

В любом случае, вы должны сдвигать диапазоны, которые собираетесь заменить после каждоговызов 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];
}

Это прекрасно работает и поддерживает отмену, как и ожидалось, отмена этой операции приводит к отмене всех замен сразу.

В любом случае, благодаря Джошу,поскольку его ответ заставил меня указать правильное направление.

0 голосов
/ 07 апреля 2011

Я удивлен, что отмена не сгруппирована автоматически. Тем не менее, вы можете сделать ручную отмену группировки; вам придется настроить обратные действия самостоятельно. Надеюсь, это укажет вам правильное направление:

- (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
}
...