Optimize syntax highlight applying

This commit is contained in:
1024jp 2016-05-06 05:30:50 +09:00
parent 63dc3b5928
commit fd4efb6eed
3 changed files with 70 additions and 36 deletions

View File

@ -7,6 +7,7 @@ develop
### Improvements
- Optimize performance to apply syntax highlight to document significantly.
- Enable move between input fields in syntax style editor with Tab key.

View File

@ -156,7 +156,7 @@ static CGFloat kPerCompoIncrement;
// ------------------------------------------------------
///
- (nonnull NSArray<NSValue *> *)rangesOfSimpleWords:(nonnull NSDictionary<NSNumber *, NSArray *> *)wordsDict ignoreCaseWords:(nonnull NSDictionary<NSNumber *, NSArray *> *)icWordsDict charSet:(nonnull NSCharacterSet *)charSet string:(nonnull NSString *)string range:(NSRange)parseRange
- (nullable NSArray<NSValue *> *)rangesOfSimpleWords:(nonnull NSDictionary<NSNumber *, NSArray *> *)wordsDict ignoreCaseWords:(nonnull NSDictionary<NSNumber *, NSArray *> *)icWordsDict charSet:(nonnull NSCharacterSet *)charSet string:(nonnull NSString *)string range:(NSRange)parseRange
// ------------------------------------------------------
{
NSMutableArray<NSValue *> *ranges = [NSMutableArray array];
@ -166,7 +166,7 @@ static CGFloat kPerCompoIncrement;
[scanner setScanLocation:parseRange.location];
while(![scanner isAtEnd] && ([scanner scanLocation] < NSMaxRange(parseRange))) {
if ([self isCancelled]) { return @[]; }
if ([self isCancelled]) { return nil; }
@autoreleasepool {
NSString *scannedString = nil;
@ -197,10 +197,10 @@ static CGFloat kPerCompoIncrement;
// ------------------------------------------------------
///
- (nonnull NSArray<NSValue *> *)rangesOfString:(nonnull NSString *)searchString ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
- (nullable NSArray<NSValue *> *)rangesOfString:(nonnull NSString *)searchString ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
// ------------------------------------------------------
{
if ([searchString length] == 0) { return @[]; }
if ([searchString length] == 0) { return nil; }
NSMutableArray<NSValue *> *ranges = [NSMutableArray array];
NSUInteger length = [searchString length];
@ -211,7 +211,7 @@ static CGFloat kPerCompoIncrement;
[scanner setScanLocation:parseRange.location];
while(![scanner isAtEnd] && ([scanner scanLocation] < NSMaxRange(parseRange))) {
if ([self isCancelled]) { return @[]; }
if ([self isCancelled]) { return nil; }
@autoreleasepool {
[scanner scanUpToString:searchString intoString:nil];
@ -232,10 +232,10 @@ static CGFloat kPerCompoIncrement;
// ------------------------------------------------------
///
- (nonnull NSArray<NSValue *> *)rangesOfBeginString:(nonnull NSString *)beginString endString:(nonnull NSString *)endString ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
- (nullable NSArray<NSValue *> *)rangesOfBeginString:(nonnull NSString *)beginString endString:(nonnull NSString *)endString ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
// ------------------------------------------------------
{
if ([beginString length] == 0) { return @[]; }
if ([beginString length] == 0) { return nil; }
NSMutableArray<NSValue *> *ranges = [NSMutableArray array];
NSUInteger endLength = [endString length];
@ -246,7 +246,7 @@ static CGFloat kPerCompoIncrement;
[scanner setScanLocation:parseRange.location];
while(![scanner isAtEnd] && ([scanner scanLocation] < NSMaxRange(parseRange))) {
if ([self isCancelled]) { return @[]; }
if ([self isCancelled]) { return nil; }
@autoreleasepool {
[scanner scanUpToString:beginString intoString:nil];
@ -280,10 +280,10 @@ static CGFloat kPerCompoIncrement;
// ------------------------------------------------------
///
- (nonnull NSArray<NSValue *> *)rangesOfRegularExpressionString:(nonnull NSString *)regexStr ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
- (nullable NSArray<NSValue *> *)rangesOfRegularExpressionString:(nonnull NSString *)regexStr ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
// ------------------------------------------------------
{
if ([regexStr length] == 0) { return @[]; }
if ([regexStr length] == 0) { return nil; }
NSMutableArray<NSValue *> *ranges = [NSMutableArray array];
NSRegularExpressionOptions options = NSRegularExpressionAnchorsMatchLines;
@ -295,7 +295,7 @@ static CGFloat kPerCompoIncrement;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexStr options:options error:&error];
if (error) {
NSLog(@"ERROR in \"%s\"", __PRETTY_FUNCTION__);
return @[];
return nil;
}
__weak typeof(self) weakSelf = self;
@ -318,7 +318,7 @@ static CGFloat kPerCompoIncrement;
// ------------------------------------------------------
///
- (nonnull NSArray<NSValue *> *)rangesOfRegularExpressionBeginString:(nonnull NSString *)beginString endString:(nonnull NSString *)endString ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
- (nullable NSArray<NSValue *> *)rangesOfRegularExpressionBeginString:(nonnull NSString *)beginString endString:(nonnull NSString *)endString ignoreCase:(BOOL)ignoreCase string:(nonnull NSString *)string range:(NSRange)parseRange
// ------------------------------------------------------
{
NSMutableArray<NSValue *> *ranges = [NSMutableArray array];
@ -333,7 +333,7 @@ static CGFloat kPerCompoIncrement;
if (error) {
NSLog(@"ERROR in \"%s\"", __PRETTY_FUNCTION__);
return @[];
return nil;
}
__weak typeof(self) weakSelf = self;
@ -393,8 +393,8 @@ static CGFloat kPerCompoIncrement;
QCLocationKey: @(range.location),
QCLengthKey: @([endDelimiter length])}];
}
}
if ([self inlineCommentDelimiter]) {
NSString *delimiter = [self inlineCommentDelimiter];
NSArray<NSValue *> *ranges = [self rangesOfString:delimiter ignoreCase:NO string:string range:parseRange];
@ -410,9 +410,7 @@ static CGFloat kPerCompoIncrement;
QCStartEndKey: @(QCEnd),
QCLocationKey: @(NSMaxRange(lineRange)),
QCLengthKey: @0U}];
}
}
//
@ -539,7 +537,7 @@ static CGFloat kPerCompoIncrement;
if ([beginStr length] == 0) { return; } // continue
NSArray<NSValue *> *extractedRanges = @[];
NSArray<NSValue *> *extractedRanges = nil;
if ([strDict[CESyntaxRegularExpressionKey] boolValue]) {
if ([endStr length] > 0) {
@ -566,18 +564,20 @@ static CGFloat kPerCompoIncrement;
@synchronized(simpleWordsDict) {
NSMutableDictionary<NSNumber *, NSMutableArray<NSString *> *> *dict = ignoresCase ? simpleICWordsDict : simpleWordsDict;
NSString *word = ignoresCase ? [beginStr lowercaseString] : beginStr;
NSMutableArray<NSString *> *wordsArray = dict[len];
if (wordsArray) {
[wordsArray addObject:word];
NSMutableArray<NSString *> *words = dict[len];
if (words) {
[words addObject:word];
} else {
wordsArray = [NSMutableArray arrayWithObject:word];
dict[len] = wordsArray;
words = [NSMutableArray arrayWithObject:word];
dict[len] = words;
}
}
}
}
if (!extractedRanges) { return; } // continue
@synchronized(ranges) {
[ranges addObjectsFromArray:extractedRanges];
}
@ -592,11 +592,14 @@ static CGFloat kPerCompoIncrement;
if ([self isCancelled]) { return @{}; }
if ([simpleWordsDict count] > 0 || [simpleICWordsDict count] > 0) {
[ranges addObjectsFromArray:[self rangesOfSimpleWords:simpleWordsDict
ignoreCaseWords:simpleICWordsDict
charSet:[self simpleWordsCharacterSets][syntaxKey]
string:string
range:parseRange]];
NSArray<NSValue *> *extractedRanges = [self rangesOfSimpleWords:simpleWordsDict
ignoreCaseWords:simpleICWordsDict
charSet:[self simpleWordsCharacterSets][syntaxKey]
string:string
range:parseRange];
if (extractedRanges) {
[ranges addObjectsFromArray:extractedRanges];
}
}
// store range array
highlights[syntaxKey] = ranges;
@ -616,13 +619,47 @@ static CGFloat kPerCompoIncrement;
if ([self isCancelled]) { return @{}; }
return [highlights copy];
return sanitizeHighlights(highlights);
}
#pragma mark Private Functions
// ------------------------------------------------------
/// remove duplicated coloring ranges
NSDictionary<NSString *, NSArray<NSValue *> *> *sanitizeHighlights(NSDictionary<NSString *, NSArray<NSValue *> *> *highlights)
// ------------------------------------------------------
{
// This sanitization will reduce performance time of `applyHighlights:highlights:layoutManager:` significantly.
// Adding temporary attribute to a layoutManager is quite sluggish,
// so we want to remove useless highlighting ranges as many as possible beforehand.
NSMutableDictionary *sanitizedHighlights = [NSMutableDictionary dictionaryWithCapacity:[highlights count]];
NSMutableIndexSet *highlightedIndexes = [NSMutableIndexSet indexSet];
for (NSString *syntaxType in [kSyntaxDictKeys reverseObjectEnumerator]) {
NSArray<NSValue *> *ranges = highlights[syntaxType];
NSMutableArray<NSValue *> *sanitizedRanges = [NSMutableArray array];
for (NSValue *rangeValue in ranges) {
NSRange range = [rangeValue rangeValue];
if (![highlightedIndexes containsIndexesInRange:range]) {
[sanitizedRanges addObject:rangeValue];
[highlightedIndexes addIndexesInRange:range];
}
}
if ([sanitizedRanges count] > 0) {
sanitizedHighlights[syntaxType] = [sanitizedRanges copy];
}
}
return [sanitizedHighlights copy];
}
// ------------------------------------------------------
///
BOOL isCharacterEscaped(NSString *string, NSUInteger location)

View File

@ -48,7 +48,7 @@ static NSString *_Nonnull const kAllAlphabetChars = @"abcdefghijklmnopqrstuvwxyz
@property (nonatomic, nullable, copy) NSDictionary<NSString *, NSCharacterSet *> *simpleWordsCharacterSets;
@property (nonatomic, nullable, copy) NSDictionary<NSString *, NSString *> *pairedQuoteTypes; // dict for quote pair to extract with comment
@property (nonatomic, nullable, copy) NSDictionary *cachedHighlights; // extracted results cache of the last whole string highlighs
@property (nonatomic, nullable, copy) NSDictionary<NSString *, NSArray *> *cachedHighlights; // extracted results cache of the last whole string highlighs
@property (nonatomic, nullable, copy) NSString *cachedHash; // MD5 hash
@ -442,7 +442,7 @@ static NSArray<NSString *> *kSyntaxDictKeys;
}
__weak typeof(self) weakSelf = self;
[parser parseRange:highlightRange completionHandler:^(NSDictionary<NSString *,NSArray<NSValue *> *> * _Nonnull highlights)
[parser parseRange:highlightRange completionHandler:^(NSDictionary<NSString *, NSArray<NSValue *> *> * _Nonnull highlights)
{
typeof(self) self = weakSelf; // strong self
if (!self) { // This block can be passed if the syntax style is already discarded.
@ -503,18 +503,14 @@ static NSArray<NSString *> *kSyntaxDictKeys;
- (void)applyHighlights:(NSDictionary<NSString *, NSArray<NSValue *> *> *)highlights range:(NSRange)highlightRange layoutManager:(nonnull NSLayoutManager *)layoutManager
// ------------------------------------------------------
{
//
// remove current highlights
[layoutManager removeTemporaryAttribute:NSForegroundColorAttributeName
forCharacterRange:highlightRange];
//
// apply color to layoutManager
CETheme *theme = [(NSTextView<CETextViewProtocol> *)[layoutManager firstTextView] theme];
for (NSString *syntaxType in kSyntaxDictKeys) {
NSArray<NSValue *> *ranges = highlights[syntaxType];
if ([ranges count] == 0) { continue; }
// get color from theme
NSColor *color = [theme syntaxColorForType:syntaxType] ?: [theme textColor];
for (NSValue *rangeValue in ranges) {