1/* 2 HKSyntaxHighlighter.m 3 4 Implementation of the HKSyntaxHighlighter class for the HighlighterKit 5 framework. 6 7 Copyright (C) 2005, 2006 Saso Kiselkov 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 2 of the License, or 12 (at your option) any later version. 13 14 This program is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with this program; if not, write to the Free Software 21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22*/ 23 24#import "HKSyntaxHighlighter.h" 25 26#import <Foundation/NSArray.h> 27#import <Foundation/NSAutoreleasePool.h> 28#import <Foundation/NSBundle.h> 29#import <Foundation/NSCharacterSet.h> 30#import <Foundation/NSDebug.h> 31#import <Foundation/NSDictionary.h> 32#import <Foundation/NSException.h> 33#import <Foundation/NSLock.h> 34#import <Foundation/NSNotification.h> 35#import <Foundation/NSNull.h> 36#import <Foundation/NSString.h> 37#import <Foundation/NSUserDefaults.h> 38#import <Foundation/NSValue.h> 39 40#import <AppKit/NSAttributedString.h> 41#import <AppKit/NSTextStorage.h> 42 43#import "HKSyntaxDefinition.h" 44 45static NSString * const KeywordsNotFixedAttributeName = @"KNF"; 46static NSString * const ContextAttributeName = @"C"; 47 48static inline BOOL 49my_isspace(unichar c) 50{ 51 if (c == ' ' || c == '\t' || c == '\f') 52 { 53 return YES; 54 } 55 else 56 { 57 return NO; 58 } 59} 60 61/** 62 * This function looks ahead and after `startRange' in `string' and 63 * tries to return the range of a whitespace delimited word at the 64 * specified range. E.g. string = @"abc def ghi" and startRange = {5, 1}, 65 * then {4, 3} is returned, because the word "def" lies within the range. 66 * Please note that even when the range points to a whitespace area 67 * (e.g. string = @"abc def" and startRange = {3, 1}), the lookup 68 * will occur and not return `not found' (e.g. in the above example it 69 * would return {0, 7}). When the range is also surrounded by whitespace 70 * (e.g. @" " and startRange = {1, 1}) the startRange itself is returned. 71 */ 72static NSRange 73RangeOfWordInString(NSString * string, NSRange startRange) 74{ 75 SEL sel = @selector(characterAtIndex:); 76 unichar (*characterAtIndex)(id, SEL, unsigned int) = 77 (unichar (*)(id, SEL, unsigned int)) [string methodForSelector: sel]; 78 int ahead, after; 79 unsigned int length = [string length]; 80 81 for (ahead = 1; ahead <= (int) startRange.location; ahead++) 82 { 83 if (my_isspace(characterAtIndex(string, 84 sel, 85 startRange.location - ahead))) 86 { 87 break; 88 } 89 } 90 ahead--; 91 92 for (after = 0; (after + NSMaxRange(startRange)) < length; after++) 93 { 94 if (my_isspace(characterAtIndex(string, 95 sel, 96 (after + NSMaxRange(startRange))))) 97 { 98 break; 99 } 100 } 101 102 { 103 unsigned int start = startRange.location - ahead, 104 length = startRange.length + ahead + after; 105 106 if (start > 0) 107 { 108 start--; 109 length++; 110 } 111 if (length + 1 < length) 112 { 113 length++; 114 } 115 116 return NSMakeRange(start, length); 117 } 118} 119 120static inline BOOL 121LocateString(NSString * str, 122 unichar * buf, 123 unsigned int length, 124 unsigned int offset) 125{ 126 unsigned int i, n; 127 128 for (i = 0, n = [str length]; i < n; i++) 129 { 130 if (i >= length) 131 { 132 return NO; 133 } 134 135 if (buf[i + offset] != [str characterAtIndex: i]) 136 { 137 return NO; 138 } 139 } 140 141 return YES; 142} 143 144@interface HKSyntaxHighlighter (Private) 145 146- (void) fixUpContextsInRange: (NSRange) r; 147 148- (void) fixUpKeywordsInRange: (NSRange) r; 149- (void) lazilyFixUpKeywordsInRange: (NSRange) r; 150 151- (void) assignGraphicalAttributesOfContext: (unsigned int) context 152 toRange: (NSRange) r; 153 154- (void) assignGraphicalAttributesOfKeyword: (unsigned int) keyword 155 inContext: (unsigned int) context 156 toRange: (NSRange) r; 157 158- (int) contextBeforeRange: (NSRange) r; 159- (int) contextAfterRange: (NSRange) r; 160- (int) contextAtEndOfRange: (NSRange) r; 161 162- (void) beginEditingIfNeeded; 163- (void) endEditingIfNeeded; 164 165@end 166 167@implementation HKSyntaxHighlighter (Private) 168 169/** 170 * Fixes up the contexts inside the text storage in range `r'. A context 171 * is recognized by the "Context" attribute which holds the number of 172 * the context. This method also applies graphical attributes of the 173 * corresponding contexts to the context ranges. 174 */ 175- (void) fixUpContextsInRange: (NSRange) r 176{ 177 HKTextPattern ** beginnings = [syntax contextBeginnings]; 178 const char * beginningChars = [syntax contextBeginningCharacters]; 179 unsigned numBeginningChars = [syntax numberOfContextBeginningCharacters]; 180 181 unsigned int i; 182 unichar * string; 183 unsigned int context; 184 185 string = (unichar *) malloc(r.length * sizeof(unichar)); 186 [[textStorage string] getCharacters: string range: r]; 187 188 i = 0; 189 context = [self contextBeforeRange: r]; 190 while (i < r.length) 191 { 192 // marks the beginning of the currently processed range 193 unsigned int mark = i; 194 195 // default context - look for beginning symbols 196 if (context == 0) 197 { 198 unsigned int j = 0; 199 HKTextPattern * pattern = NULL; 200 NSRange ctxtRange; 201 int l = 0; 202 HKTextPattern ** skips = [syntax contextSkipsForContext: 0]; 203 const char * skipChars = [syntax contextSkipCharactersForContext: 0]; 204 unsigned int numSkipChars = [syntax 205 numberOfContextSkipCharactersForContext: 0]; 206 207 for (;i < r.length; i++) 208 { 209 unichar c = string[i]; 210 211 // Optimize - look into the skip characters array if the 212 // character could be the beginning of a skip sequence. 213 // If not, don't perform skip sequence recognition at all. 214 if (c < numSkipChars && skipChars[c]) 215 { 216 for (j = 0; (pattern = skips[j]) != NULL; j++) 217 { 218 l = HKCheckTextPatternPresenceInString (pattern, 219 string, 220 r.length, 221 i); 222 if (l > 0) 223 { 224 break; 225 } 226 } 227 228 if (l > 0) 229 { 230 i += l - 1; 231 continue; 232 } 233 } 234 235 // optimize - skip unneeded characters 236 if (c < numBeginningChars && !beginningChars[c]) 237 { 238 continue; 239 } 240 241 for (j = 0; (pattern = beginnings[j]) != NULL; j++) 242 { 243 l = HKCheckTextPatternPresenceInString(pattern, string, 244 r.length, i); 245 if (l > 0) 246 { 247 break; 248 } 249 } 250 251 if (l > 0) 252 { 253 break; 254 } 255 } 256 257 // non-default contexts begin with number 1, not zero 258 j++; 259 260 ctxtRange = NSMakeRange(r.location + mark, i - mark); 261 if (ctxtRange.length > 0) 262 { 263 // add an attribute telling the context into the text storage 264 [textStorage addAttribute: ContextAttributeName 265 value: [NSNumber numberWithInt: 0] 266 range: ctxtRange]; 267 [self assignGraphicalAttributesOfContext: 0 toRange: ctxtRange]; 268 } 269 270 ctxtRange = NSMakeRange(r.location + i, l); 271 if (ctxtRange.length > 0) 272 { 273 [textStorage addAttribute: ContextAttributeName 274 value: [NSNumber numberWithInt: j] 275 range: ctxtRange]; 276 [self assignGraphicalAttributesOfContext: j 277 toRange: ctxtRange]; 278 } 279 i += l; 280 281 // switch to the found context again 282 context = j; 283 } 284 // specific context - look for it's terminator, but skip it's 285 // exceptions 286 else 287 { 288 int l = 0; 289 HKTextPattern * ending = [syntax contextEndingForContext: context - 1]; 290 NSRange ctxtRange; 291 HKTextPattern ** skips = [syntax contextSkipsForContext: context]; 292 const char * skipChars = [syntax contextSkipCharactersForContext: 293 context]; 294 unsigned int numSkipChars = [syntax 295 numberOfContextSkipCharactersForContext: context]; 296 297 for (;i < r.length; i++) 298 { 299 unichar c = string[i]; 300 unsigned int j; 301 302 if (c < numSkipChars && skipChars[c]) 303 { 304 unsigned int j; 305 HKTextPattern * pattern; 306 307 for (j = 0; (pattern = skips[j]) != NULL; j++) 308 { 309 l = HKCheckTextPatternPresenceInString(pattern, string, 310 r.length, i); 311 if (l > 0) 312 { 313 break; 314 } 315 } 316 317 if (l > 0) 318 { 319 i += l - 1; 320 continue; 321 } 322 } 323 324 l = HKCheckTextPatternPresenceInString(ending, string, 325 r.length, i); 326 if (l > 0) 327 { 328 break; 329 } 330 } 331 332 ctxtRange = NSMakeRange(r.location + mark, i - mark); 333 if (ctxtRange.length > 0) 334 { 335 // add an attribute telling the context into the 336 // text storage 337 [textStorage addAttribute: ContextAttributeName 338 value: [NSNumber numberWithInt: context] 339 range: ctxtRange]; 340 [self assignGraphicalAttributesOfContext: context 341 toRange: ctxtRange]; 342 } 343 344 ctxtRange = NSMakeRange(r.location + i, l); 345 if (ctxtRange.length > 0) 346 { 347 [textStorage addAttribute: ContextAttributeName 348 value: [NSNumber numberWithInt: 0] 349 range: ctxtRange]; 350 [self assignGraphicalAttributesOfContext: context 351 toRange: ctxtRange]; 352 } 353 i += l; 354 355 // switch to the default context again 356 context = 0; 357 } 358 } 359 360 free(string); 361} 362 363- (void) fixUpKeywordsInRange: (NSRange) r 364{ 365 unichar * string; 366 unsigned int i; 367 368 string = malloc(r.length * sizeof(unichar)); 369 [[textStorage string] getCharacters: string range: r]; 370 371 for (i = 0; i < r.length;) 372 { 373 NSRange contextRange; 374 HKTextPattern ** patterns; 375 int context; 376 377 context = [[textStorage attribute: ContextAttributeName 378 atIndex: i + r.location 379 effectiveRange: &contextRange] intValue]; 380 381 contextRange = NSIntersectionRange(r, contextRange); 382 contextRange.location -= r.location; 383 384 patterns = [syntax keywordsInContext: context]; 385 386 while (i < NSMaxRange(contextRange)) 387 { 388 unichar c = string[i]; 389 unsigned int l = 0; 390 unsigned int j; 391 HKTextPattern * pattern; 392 393 // skip whitespace - it can't start a keyword 394 if (my_isspace(c) || c == '\r' || c == '\n') 395 { 396 i++; 397 continue; 398 } 399 400 for (j = 0; (pattern = patterns[j]) != NULL; j++) 401 { 402 l = HKCheckTextPatternPresenceInString (pattern, 403 string, 404 r.length, 405 i); 406 if (l > 0) 407 { 408 break; 409 } 410 } 411 412 // found a pattern? 413 if (pattern != NULL) 414 { 415 NSRange keywordRange = NSMakeRange(i + r.location, l); 416 417 [self assignGraphicalAttributesOfKeyword: j 418 inContext: context 419 toRange: keywordRange]; 420 i += l; 421 } 422 else 423 { 424 i++; 425 } 426 } 427 } 428 429 free(string); 430} 431 432- (void) lazilyFixUpKeywordsInRange: (NSRange) r 433{ 434 unsigned int i; 435 BOOL localDidBeginEditing = NO; 436 437 for (i = r.location; i < NSMaxRange(r);) 438 { 439 NSRange effectiveRange; 440 441 // locate non-fixed areas and fix them up 442 if ([textStorage attribute: KeywordsNotFixedAttributeName 443 atIndex: i 444 longestEffectiveRange: &effectiveRange 445 inRange: r] != nil) 446 { 447 if (localDidBeginEditing == NO) 448 { 449 localDidBeginEditing = YES; 450 [textStorage beginEditing]; 451 } 452 effectiveRange = NSIntersectionRange(effectiveRange, r); 453 [self fixUpKeywordsInRange: effectiveRange]; 454 [textStorage removeAttribute: KeywordsNotFixedAttributeName 455 range: effectiveRange]; 456 i += effectiveRange.length; 457 } 458 459 // skip over fixed areas 460 else 461 { 462 i += effectiveRange.length; 463 } 464 } 465 466 if (localDidBeginEditing == YES) 467 { 468 [textStorage endEditing]; 469 } 470} 471 472- (void) assignGraphicalAttributesOfContext: (unsigned int) ctxt 473 toRange: (NSRange) r 474{ 475 BOOL bold, italic; 476 NSColor * color; 477 478 color = [syntax foregroundColorForContext: ctxt]; 479 if (color != nil) 480 { 481 [textStorage addAttribute: NSForegroundColorAttributeName 482 value: color 483 range: r]; 484 } 485 else if (defaultTextColor != nil) 486 { 487 [textStorage addAttribute: NSForegroundColorAttributeName 488 value: defaultTextColor 489 range: r]; 490 } 491 else 492 { 493 [textStorage removeAttribute: NSForegroundColorAttributeName range: r]; 494 } 495 496 color = [syntax backgroundColorForContext: ctxt]; 497 if (color != nil) 498 { 499 [textStorage addAttribute: NSBackgroundColorAttributeName 500 value: color 501 range: r]; 502 } 503 else 504 { 505 [textStorage removeAttribute: NSBackgroundColorAttributeName range: r]; 506 } 507 508 bold = [syntax isBoldFontForContext: ctxt]; 509 italic = [syntax isItalicFontForContext: ctxt]; 510 if (bold && italic) 511 { 512 [textStorage addAttribute: NSFontAttributeName 513 value: boldItalicFont 514 range: r]; 515 } 516 else if (bold) 517 { 518 [textStorage addAttribute: NSFontAttributeName 519 value: boldFont 520 range: r]; 521 } 522 else if (italic) 523 { 524 [textStorage addAttribute: NSFontAttributeName 525 value: italicFont 526 range: r]; 527 } 528 else 529 { 530 [textStorage addAttribute: NSFontAttributeName 531 value: normalFont 532 range: r]; 533 } 534} 535 536- (void) assignGraphicalAttributesOfKeyword: (unsigned int) keyword 537 inContext: (unsigned int) context 538 toRange: (NSRange) r 539{ 540 BOOL bold, italic; 541 NSColor * color; 542 543 color = [syntax foregroundColorForKeyword: keyword inContext: context]; 544 if (color != nil) 545 { 546 [textStorage addAttribute: NSForegroundColorAttributeName 547 value: color 548 range: r]; 549 } 550 else 551 { 552 color = [syntax foregroundColorForContext: context]; 553 554 if (color != nil) 555 { 556 [textStorage addAttribute: NSForegroundColorAttributeName 557 value: color 558 range: r]; 559 } 560 else if (defaultTextColor != nil) 561 { 562 [textStorage addAttribute: NSForegroundColorAttributeName 563 value: defaultTextColor 564 range: r]; 565 } 566 else 567 { 568 [textStorage removeAttribute: NSForegroundColorAttributeName 569 range: r]; 570 } 571 } 572 573 color = [syntax backgroundColorForKeyword: keyword inContext: context]; 574 if (color != nil) 575 { 576 [textStorage addAttribute: NSBackgroundColorAttributeName 577 value: color 578 range: r]; 579 } 580 else 581 { 582 color = [syntax backgroundColorForContext: context]; 583 584 if (color != nil) 585 { 586 [textStorage addAttribute: NSBackgroundColorAttributeName 587 value: color 588 range: r]; 589 } 590 else 591 { 592 [textStorage removeAttribute: NSBackgroundColorAttributeName 593 range: r]; 594 } 595 } 596 597 bold = [syntax isBoldFontForKeyword: keyword inContext: context]; 598 italic = [syntax isItalicFontForKeyword: keyword inContext: context]; 599 if (bold && italic) 600 { 601 [textStorage addAttribute: NSFontAttributeName 602 value: boldItalicFont 603 range: r]; 604 } 605 else if (bold) 606 { 607 [textStorage addAttribute: NSFontAttributeName 608 value: boldFont 609 range: r]; 610 } 611 else if (italic) 612 { 613 [textStorage addAttribute: NSFontAttributeName 614 value: italicFont 615 range: r]; 616 } 617 else 618 { 619 [textStorage addAttribute: NSFontAttributeName 620 value: normalFont 621 range: r]; 622 } 623} 624 625- (int) contextBeforeRange: (NSRange) r 626{ 627 NSRange tmp; 628 629 if (r.location == 0) 630 { 631 return 0; 632 } 633 else 634 { 635 return [[textStorage attribute: ContextAttributeName 636 atIndex: r.location - 1 637 effectiveRange: &tmp] intValue]; 638 } 639} 640 641- (int) contextAfterRange: (NSRange) r 642{ 643 NSRange tmp; 644 unsigned int i, length; 645 646 i = NSMaxRange(r); 647 length = [textStorage length]; 648 649 if (length == 0) 650 { 651 return 0; 652 } 653 else if (i < length) 654 { 655 return [[textStorage attribute: ContextAttributeName 656 atIndex: i 657 effectiveRange: &tmp] intValue]; 658 } 659 else 660 { 661 return 0; 662 } 663} 664 665- (int) contextAtEndOfRange: (NSRange) r 666{ 667 NSRange tmp; 668 int i = (int) NSMaxRange(r) - 1; 669 670 if (i < 0) 671 { 672 return 0; 673 } 674 else 675 { 676 return [[textStorage attribute: ContextAttributeName 677 atIndex: i 678 effectiveRange: &tmp] intValue]; 679 } 680} 681 682- (void) beginEditingIfNeeded 683{ 684 if (didBeginEditing == NO) 685 { 686 didBeginEditing = YES; 687 [textStorage beginEditing]; 688 } 689} 690 691- (void) endEditingIfNeeded 692{ 693 if (didBeginEditing == YES) 694 { 695 didBeginEditing = NO; 696 [textStorage endEditing]; 697 } 698} 699 700@end 701 702@implementation HKSyntaxHighlighter 703 704+ (NSFont *) defaultFont 705{ 706 NSUserDefaults * df = [NSUserDefaults standardUserDefaults]; 707 NSString * fontName; 708 float fontSize; 709 NSFont * font = nil; 710 711 fontName = [df objectForKey: @"HKFont"]; 712 fontSize = [df floatForKey: @"HKFontSize"]; 713 714 if (fontName != nil) 715 { 716 font = [NSFont fontWithName: fontName size: fontSize]; 717 } 718 if (font == nil) 719 { 720 font = [NSFont userFixedPitchFontOfSize: fontSize]; 721 } 722 723 return font; 724} 725 726+ (NSFont *) defaultBoldFont 727{ 728 NSFont * font = [self defaultFont]; 729 730 return [[NSFontManager sharedFontManager] convertFont: font 731 toHaveTrait: NSBoldFontMask]; 732} 733 734+ (NSFont *) defaultItalicFont 735{ 736 NSFont * font = [self defaultFont]; 737 738 return [[NSFontManager sharedFontManager] convertFont: font 739 toHaveTrait: NSItalicFontMask]; 740} 741 742+ (NSFont *) defaultBoldItalicFont 743{ 744 NSFont * font = [self defaultFont]; 745 746 return [[NSFontManager sharedFontManager] convertFont: font 747 toHaveTrait: NSBoldFontMask | 748 NSItalicFontMask]; 749} 750 751 752- initWithHighlighterType: (NSString *) type 753 textStorage: (NSTextStorage *) aStorage 754 defaultTextColor: (NSColor *) aColor 755{ 756 HKSyntaxDefinition * def = [HKSyntaxDefinition 757 syntaxDefinitionForType: type]; 758 759 return [self initWithSyntaxDefinition: def 760 textStorage: aStorage 761 defaultTextColor: aColor]; 762} 763 764- initWithSyntaxDefinition: (HKSyntaxDefinition *) aSyntaxDefinition 765 textStorage: (NSTextStorage *) aStorage 766 defaultTextColor: (NSColor *) aColor 767{ 768 if ((self = [self init]) != nil) 769 { 770 NSRange r; 771 772 ASSIGN (textStorage, aStorage); 773 ASSIGN (syntax, aSyntaxDefinition); 774 775 // no syntax definition - no highlighting possible 776 if (syntax == nil) 777 { 778 [self release]; 779 return nil; 780 } 781 782 // mark all of the text storage as requiring keyword fixing 783 r = NSMakeRange(0, [textStorage length]); 784 [textStorage addAttribute: KeywordsNotFixedAttributeName 785 value: [NSNull null] 786 range: r]; 787 788 [[NSNotificationCenter defaultCenter] 789 addObserver: self 790 selector: @selector(textStorageWillProcessEditing:) 791 name: NSTextStorageWillProcessEditingNotification 792 object: textStorage]; 793 794 ASSIGN (normalFont, [[self class] defaultFont]); 795 ASSIGN (boldFont, [[self class] defaultBoldFont]); 796 ASSIGN (italicFont, [[self class] defaultItalicFont]); 797 ASSIGN (boldItalicFont, [[self class] defaultBoldItalicFont]); 798 799 ASSIGN (defaultTextColor, aColor); 800 801 return self; 802 } 803 else 804 { 805 return nil; 806 } 807} 808 809- (void) dealloc 810{ 811 NSDebugLLog(@"HKSyntaxHighlighter", @"HKSyntaxHighlighter: dealloc"); 812 813 [[NSNotificationCenter defaultCenter] removeObserver: self]; 814 815 TEST_RELEASE (textStorage); 816 TEST_RELEASE (syntax); 817 TEST_RELEASE (normalFont); 818 TEST_RELEASE (boldFont); 819 TEST_RELEASE (italicFont); 820 TEST_RELEASE (boldItalicFont); 821 822 TEST_RELEASE (defaultTextColor); 823 824 [super dealloc]; 825} 826 827- (void) highlightRange: (NSRange) r 828{ 829 if (delayedProcessedRange.length > 0) 830 { 831 [self beginEditingIfNeeded]; 832 [self fixUpContextsInRange: delayedProcessedRange]; 833 [self fixUpKeywordsInRange: delayedProcessedRange]; 834 835 if ([self contextAtEndOfRange: delayedProcessedRange] != 836 [self contextAfterRange: delayedProcessedRange]) 837 { 838 NSRange invalidatedRange; 839 840 lastProcessedContextIndex = NSMaxRange(delayedProcessedRange); 841 842 invalidatedRange = NSMakeRange(NSMaxRange(delayedProcessedRange), 843 [textStorage length] - NSMaxRange(delayedProcessedRange)); 844 [textStorage addAttribute: KeywordsNotFixedAttributeName 845 value: [NSNull null] 846 range: invalidatedRange]; 847 } 848 } 849 else 850 { 851 if (delayedProcessedRange.location > 0 && 852 [self contextBeforeRange: delayedProcessedRange] != 853 [self contextAfterRange: delayedProcessedRange]) 854 { 855 NSRange invalidatedRange; 856 857 lastProcessedContextIndex = NSMaxRange(delayedProcessedRange); 858 859 [self beginEditingIfNeeded]; 860 861 invalidatedRange = NSMakeRange(NSMaxRange(delayedProcessedRange), 862 [textStorage length] - NSMaxRange(delayedProcessedRange)); 863 [textStorage addAttribute: KeywordsNotFixedAttributeName 864 value: [NSNull null] 865 range: invalidatedRange]; 866 } 867 } 868 869 delayedProcessedRange = NSMakeRange(0, 0); 870 871 r = RangeOfWordInString([textStorage string], r); 872 873 // need to fixup contexts? 874 if (NSMaxRange(r) > lastProcessedContextIndex) 875 { 876 unsigned int prevContext; 877 NSRange fixupRange; 878 879 fixupRange = NSMakeRange(lastProcessedContextIndex, 880 NSMaxRange(r) - lastProcessedContextIndex); 881 882 [self beginEditingIfNeeded]; 883 [self fixUpContextsInRange: fixupRange]; 884 885 lastProcessedContextIndex = NSMaxRange(r); 886 } 887 888 [self lazilyFixUpKeywordsInRange: r]; 889 890 [self endEditingIfNeeded]; 891} 892 893- (void) textStorageWillProcessEditing: (NSNotification *) notif 894{ 895 if ([textStorage editedMask] & NSTextStorageEditedCharacters) 896 { 897 NSRange editedRange = [textStorage editedRange]; 898 899 delayedProcessedRange = RangeOfWordInString([textStorage string], 900 editedRange); 901 902 if (lastProcessedContextIndex > editedRange.location) 903 { 904 lastProcessedContextIndex += [textStorage changeInLength]; 905 } 906 } 907} 908 909@end 910