From fbb67c0e112b7e96343b430df7db0ac1b052fec7 Mon Sep 17 00:00:00 2001 From: lixiaomin Date: Wed, 19 Nov 2014 16:44:24 +0800 Subject: [PATCH 1/6] split output log item --- .../xcshareddata/MCLog.xccheckout | 18 +- MCLog/MCLog.h | 17 +- MCLog/MCLog.m | 795 +++++++++++++----- 3 files changed, 605 insertions(+), 225 deletions(-) diff --git a/MCLog.xcodeproj/project.xcworkspace/xcshareddata/MCLog.xccheckout b/MCLog.xcodeproj/project.xcworkspace/xcshareddata/MCLog.xccheckout index 69e5b99..b41d050 100644 --- a/MCLog.xcodeproj/project.xcworkspace/xcshareddata/MCLog.xccheckout +++ b/MCLog.xcodeproj/project.xcworkspace/xcshareddata/MCLog.xccheckout @@ -10,31 +10,31 @@ MCLog IDESourceControlProjectOriginsDictionary - 789CF47E-98B7-4BB5-8400-CD87C045EF74 - ssh://github.com/yuhua-chen/MCLog.git + 22F9C8F305497B927E2040B29E0DB7F039F7B3DB + https://github.com/alexlee002/MCLog IDESourceControlProjectPath - MCLog.xcodeproj/project.xcworkspace + MCLog.xcodeproj IDESourceControlProjectRelativeInstallPathDictionary - 789CF47E-98B7-4BB5-8400-CD87C045EF74 + 22F9C8F305497B927E2040B29E0DB7F039F7B3DB ../.. IDESourceControlProjectURL - ssh://github.com/yuhua-chen/MCLog.git + https://github.com/alexlee002/MCLog IDESourceControlProjectVersion - 110 + 111 IDESourceControlProjectWCCIdentifier - 789CF47E-98B7-4BB5-8400-CD87C045EF74 + 22F9C8F305497B927E2040B29E0DB7F039F7B3DB IDESourceControlProjectWCConfigurations IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey - 789CF47E-98B7-4BB5-8400-CD87C045EF74 + 22F9C8F305497B927E2040B29E0DB7F039F7B3DB IDESourceControlWCCName - MCLog_github + MCLog diff --git a/MCLog/MCLog.h b/MCLog/MCLog.h index e4bd087..c7565a1 100644 --- a/MCLog/MCLog.h +++ b/MCLog/MCLog.h @@ -8,23 +8,8 @@ #import -@class MCLogIDEConsoleArea; - -@interface NSSearchField (MCLog) -@property (nonatomic, strong) MCLogIDEConsoleArea *consoleArea; -@property (nonatomic, strong) NSTextView *consoleTextView; -@end - -@interface MCLogIDEConsoleArea : NSViewController -- (BOOL)_shouldAppendItem:(id)obj; -- (void)_clearText; -@end - @interface MCLog : NSObject + (void)pluginDidLoad:(NSBundle *)bundle; @end -void replaceShouldAppendItemMethod(); -void replaceClearTextMethod(); -NSSearchField *getSearchField(id consoleArea); -NSString *hash(id obj); \ No newline at end of file + diff --git a/MCLog/MCLog.m b/MCLog/MCLog.m index 6a7cf37..0931a96 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -8,239 +8,213 @@ #import "MCLog.h" #import +#include #define MCLOG_FLAG "MCLOG_FLAG" #define kTagSearchField 99 +#define MCLogger(fmt, ...) NSLog((@"[MCLog] %s(Line:%d) " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) + +@class MCLogIDEConsoleArea; + static NSMutableDictionary *originConsoleItemsMap; static MCLogIDEConsoleArea *consoleArea = nil; -static IMP _clearText = nil; +static NSSearchField *SearchField = nil; -@interface MCLog () -{ - NSMutableDictionary *workspace; -} -@end +NSSearchField *getSearchField(id consoleArea); +NSString *hash(id obj); +NSArray *backtraceStack(); +void hookDVTTextStorage(); +void hookIDEConsoleAdaptor(); +void hookIDEConsoleArea(); +void hookIDEConsoleItem(); -@implementation MCLog -+ (void)load -{ - NSLog(@"%s, env: %s", __PRETTY_FUNCTION__, getenv(MCLOG_FLAG)); - - if (getenv(MCLOG_FLAG) && !strcmp(getenv(MCLOG_FLAG), "YES")) { - // alreay installed plugin - return; - } - - replaceShouldAppendItemMethod(); - replaceClearTextMethod(); - originConsoleItemsMap = [NSMutableDictionary dictionary]; - setenv(MCLOG_FLAG, "YES", 0); -} +typedef NS_ENUM(NSUInteger, MCLogLevel) { + MCLogLevelVerbose = 0x1000, + MCLogLevelInfo, + MCLogLevelWarn, + MCLogLevelError +}; -+ (void)pluginDidLoad:(NSBundle *)bundle -{ - NSLog(@"%s, %@", __PRETTY_FUNCTION__, bundle); - static id sharedPlugin = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedPlugin = [[self alloc] init]; - }); -} -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} +//////////////////////////////////////////////////////////////////////////////////// +#pragma mark - NSSearchField (MCLog) +@interface NSSearchField (MCLog) +@property (nonatomic, strong) MCLogIDEConsoleArea *consoleArea; +@property (nonatomic, strong) NSTextView *consoleTextView; +@end -- (id)init -{ - self = [super init]; - if (self) { - workspace = [NSMutableDictionary dictionary]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activate:) name:@"IDEIndexWillIndexWorkspaceNotification" object:nil]; - } - return self; -} +static const void *kMCLogConsoleTextViewKey; +static const void *kMCLogIDEConsoleAreaKey; -- (NSView *)getViewByClassName:(NSString *)className andContainerView:(NSView *)container -{ - Class class = NSClassFromString(className); - for (NSView *subView in container.subviews) { - if ([subView isKindOfClass:class]) { - return subView; - } else { - NSView *view = [self getViewByClassName:className andContainerView:subView]; - if ([view isKindOfClass:class]) { - return view; - } - } - } - return nil; -} +@implementation NSSearchField (MCLog) -- (NSView *)getParantViewByClassName:(NSString *)className andView:(NSView *)view +- (void)setConsoleArea:(MCLogIDEConsoleArea *)consoleArea { - NSView *superView = view.superview; - while (superView) { - if ([[superView className] isEqualToString:className]) { - return superView; - } - superView = superView.superview; - } - - return nil; + objc_setAssociatedObject(self, &kMCLogIDEConsoleAreaKey, consoleArea, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -- (BOOL)addCustomViews +- (MCLogIDEConsoleArea *)consoleArea { - NSView *contentView = [[NSApp mainWindow] contentView]; - NSView *consoleTextView = [self getViewByClassName:@"IDEConsoleTextView" andContainerView:contentView]; - if (!consoleTextView) { - return NO; - } - - contentView = [self getParantViewByClassName:@"DVTControllerContentView" andView:consoleTextView]; - NSView *scopeBarView = [self getViewByClassName:@"DVTScopeBarView" andContainerView:contentView]; - if (!scopeBarView) { - return NO; - } - - NSButton *button = nil; - for (NSView *subView in scopeBarView.subviews) { - if ([[subView className] isEqualToString:@"NSButton"]) { - button = (NSButton *)subView; - break; - } - } - - if (!button) { - return NO; - } - - if ([scopeBarView viewWithTag:kTagSearchField]) { - return YES; - } - - NSRect frame = button.frame; - frame.origin.x -= button.frame.size.width + 205; - frame.size.width = 200.0; - frame.size.height -= 2; - - NSSearchField *searchField = [[NSSearchField alloc] initWithFrame:frame]; - searchField.autoresizingMask = NSViewMinXMargin; - searchField.font = [NSFont systemFontOfSize:11.0]; - searchField.delegate = self; - searchField.consoleTextView = (NSTextView *)consoleTextView; - searchField.tag = kTagSearchField; - [searchField.cell setPlaceholderString:@"Regular Expression"]; - [scopeBarView addSubview:searchField]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(searchFieldDidEndEditing:) name:NSControlTextDidEndEditingNotification object:nil]; - - return YES; + return objc_getAssociatedObject(self, &kMCLogIDEConsoleAreaKey); } -#pragma mark - Notifications - -- (void)searchFieldDidEndEditing:(NSNotification *)notification +- (void)setConsoleTextView:(NSTextView *)consoleTextView { - if (![[notification object] isMemberOfClass:[NSSearchField class]]) { - return; - } - - NSSearchField *searchField = [notification object]; - if (![searchField respondsToSelector:@selector(consoleTextView)]) { - return; - } - - if (![searchField respondsToSelector:@selector(consoleArea)]) { - return; - } - - NSTextView *consoleTextView = searchField.consoleTextView; - MCLogIDEConsoleArea *consoleArea = searchField.consoleArea; - - // get rid of the annoying 'undeclared selector' warning - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wundeclared-selector" - if ([consoleTextView respondsToSelector:@selector(clearConsoleItems)]) { - [consoleTextView performSelector:@selector(clearConsoleItems) withObject:nil]; - } - - NSMutableDictionary *originConsoleItems = [originConsoleItemsMap objectForKey:hash(consoleArea)]; - NSArray *sortedItems = [[originConsoleItems allValues] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { - NSTimeInterval a = [obj1 timestamp]; - NSTimeInterval b = [obj2 timestamp]; - if (a > b) { - return NSOrderedDescending; - } - - if(a < b) { - return NSOrderedAscending; - } - - return NSOrderedSame; - }]; - - if ([consoleArea respondsToSelector:@selector(_appendItems:)]) { - [consoleArea performSelector:@selector(_appendItems:) withObject:sortedItems]; - } - #pragma clang diagnostic pop + objc_setAssociatedObject(self, &kMCLogConsoleTextViewKey, consoleTextView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -- (void)activate:(NSNotification *)notification { - - id IDEIndex = [notification object]; - BOOL isAdded = [[workspace objectForKey:hash(IDEIndex)] boolValue]; - if (isAdded) { - return; - } - if ([self addCustomViews]) { - [workspace setObject:@(YES) forKey:hash(IDEIndex)]; - } +- (NSTextView *)consoleTextView +{ + return objc_getAssociatedObject(self, &kMCLogConsoleTextViewKey); } +@dynamic consoleArea; +@dynamic consoleTextView; @end -#pragma mark - NSSearchField (MCLog) -static const void *kMCLogConsoleTextViewKey; -static const void *kMCLogIDEConsoleAreaKey; +/////////////////////////////////////////////////////////////////////////////////// +#pragma mark - MCIDEConsoleItem -@implementation NSSearchField (MCLog) +@interface NSObject (MCIDEConsoleItem) +- (void)setLogLevel:(NSUInteger)loglevel; +- (NSUInteger)logLevel; -- (void)setConsoleArea:(MCLogIDEConsoleArea *)consoleArea +- (void)updateItemAttribute:(id)item; +@end + +static const void *LogLevelAssociateKey; +@implementation NSObject (MCIDEConsoleItem) + +- (void)setLogLevel:(NSUInteger)loglevel { - objc_setAssociatedObject(self, &kMCLogIDEConsoleAreaKey, consoleArea, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, &LogLevelAssociateKey, @(loglevel), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -- (MCLogIDEConsoleArea *)consoleArea +- (NSUInteger)logLevel { - return objc_getAssociatedObject(self, &kMCLogIDEConsoleAreaKey); + return [objc_getAssociatedObject(self, &LogLevelAssociateKey) unsignedIntegerValue]; } -- (void)setConsoleTextView:(NSTextView *)consoleTextView +- (void)updateItemAttribute:(id)item { - objc_setAssociatedObject(self, &kMCLogConsoleTextViewKey, consoleTextView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + NSError *error = nil; + static NSRegularExpression *TimePattern = nil; + if (TimePattern == nil) { + TimePattern = [NSRegularExpression regularExpressionWithPattern:@"^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.*" options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; + if (!TimePattern) { + MCLogger(@"%@", error); + } + } + + static NSRegularExpression *ControlCharsPattern = nil; + if (ControlCharsPattern == nil) { + ControlCharsPattern = [NSRegularExpression regularExpressionWithPattern:@"\\\\0?33\\[[\\d;]+m" options:0 error:&error]; + if (!ControlCharsPattern) { + MCLogger(@"%@", error); + } + } + + NSString *content = [item valueForKey:@"content"]; + if ([[item valueForKey:@"output"] boolValue] || [[item valueForKey:@"error"] boolValue]) { + if ([TimePattern matchesInString:content options:0 range:NSMakeRange(0, content.length)].count) { + //MCLogger(@"%@ matched pattern:'%@'", content, TimePattern); + //content = [content substringFromIndex:11]; + } + if ([[item valueForKey:@"error"] boolValue]) { + content = [NSString stringWithFormat:@"\\033[31m%@\\033[0m", content]; + } else { + NSString *originalContent = [ControlCharsPattern stringByReplacingMatchesInString:content options:0 range:NSMakeRange(0, content.length) withTemplate:@""]; + static NSString *patternString = @"\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+-\\[%@\\]\\s+.*"; + static NSRegularExpression *VerboseLogPattern = nil; + static NSRegularExpression *InfoLogPattern = nil; + static NSRegularExpression *WarnLogPattern = nil; + static NSRegularExpression *ErrorLogPattern = nil; + + if (!VerboseLogPattern) { + VerboseLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"VERBOSE"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; + if (!VerboseLogPattern) { + MCLogger(@"%@", error); + } + } + if (!InfoLogPattern) { + InfoLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"INFO"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; + if (!InfoLogPattern) { + MCLogger(@"%@", error); + } + } + if (!WarnLogPattern) { + WarnLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"WARN"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; + if (!WarnLogPattern) { + MCLogger(@"%@", error); + } + } + if (!ErrorLogPattern) { + ErrorLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"ERROR"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; + if (!ErrorLogPattern) { + MCLogger(@"%@", error); + } + } + NSRange matchingRange = (NSRange){.location = 0, .length = originalContent.length}; + if ([VerboseLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { + [item setLogLevel:MCLogLevelVerbose]; + } + else if ([InfoLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { + [item setLogLevel:MCLogLevelInfo]; + } + else if ([WarnLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { + [item setLogLevel:MCLogLevelWarn]; + } + else if ([ErrorLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { + [item setLogLevel:MCLogLevelError]; + } + } + } else { + //content = [@"\\033[0m" stringByAppendingString:content]; + } + + [item setValue:content forKey:@"content"]; } -- (NSTextView *)consoleTextView +@end + + +static IMP IDEConsoleItemInitIMP = nil; +@interface MCIDEConsoleItem : NSObject +- (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3; +@end + +@implementation MCIDEConsoleItem + +- (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3 { - return objc_getAssociatedObject(self, &kMCLogConsoleTextViewKey); + id item = IDEConsoleItemInitIMP(self, _cmd, arg1, arg2, arg3); + [self updateItemAttribute:item]; + MCLogger(@"log level:%zd", [item logLevel]); + return item; } -@dynamic consoleArea; -@dynamic consoleTextView; + @end + +/////////////////////////////////////////////////////////////////////////////////// #pragma mark - MCLogIDEConsoleArea +@interface MCLogIDEConsoleArea : NSViewController +- (BOOL)_shouldAppendItem:(id)obj; +- (void)_clearText; +@end + +static IMP originalClearTextIMP = nil; @implementation MCLogIDEConsoleArea - (BOOL)_shouldAppendItem:(id)obj; { + MCLogger(@"should append item:[%@]\n%@\nadaptorType:%@; kind:%zd", [obj class], obj, [obj valueForKey:@"adaptorType"], [obj valueForKey:@"kind"]); + NSSearchField *searchField = getSearchField(self); if (!searchField) { return YES; @@ -254,13 +228,22 @@ - (BOOL)_shouldAppendItem:(id)obj; if (!originConsoleItems) { originConsoleItems = [NSMutableDictionary dictionary]; } + +// if (originConsoleItems[@([obj timestamp])] == nil) { +// [MCLogIDEConsoleArea updateItemAttribute:obj]; +// } // store all console items. [originConsoleItems setObject:obj forKey:@([obj timestamp])]; [originConsoleItemsMap setObject:originConsoleItems forKey:hash(self)]; - + if (![searchField.stringValue length]) { - return YES; + NSInteger filterMode = [[self valueForKey:@"filterMode"] intValue]; + if (filterMode >= MCLogLevelVerbose) { + MCLogger(@"log level:%zd; filter mode:%zd", [obj logLevel], filterMode); + return [obj logLevel] >= filterMode; + } + return YES; } // test with the regular expression @@ -286,32 +269,422 @@ - (BOOL)_shouldAppendItem:(id)obj; - (void)_clearText { - _clearText(self, _cmd); + originalClearTextIMP(self, _cmd); [originConsoleItemsMap removeObjectForKey:hash(self)]; } @end -#pragma mark - -void replaceShouldAppendItemMethod() + + +/////////////////////////////////////////////////////////////////////////////////// +#pragma mark - MCDVTTextStorage +static IMP originalAppendAttributedStringIMP = nil; +static IMP originalFixAttributesInRangeIMP = nil; + +@interface MCDVTTextStorage : NSTextStorage +- (void)fixAttributesInRange:(NSRange)range; +- (void)appendAttributedString:(NSAttributedString *)attrString; +@end + +@implementation MCDVTTextStorage + +- (void)appendAttributedString:(NSAttributedString *)attrString { - Class IDEConsoleArea = NSClassFromString(@"IDEConsoleArea"); - Method originalMethod = class_getInstanceMethod([IDEConsoleArea class], @selector(_shouldAppendItem:)); - - IMP newImpl = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_shouldAppendItem:)); - method_setImplementation(originalMethod, newImpl); + MCLogger(@"self:%@\ntarget:%@", self, SearchField.consoleTextView.textStorage); + originalAppendAttributedStringIMP(self, _cmd, attrString); +// if (self == SearchField.consoleTextView.textStorage) { +// MCLogger(@"target textStorage! append attrString:%@", attrString); +// } } -void replaceClearTextMethod() +- (void)fixAttributesInRange:(NSRange)range { - Class IDEConsoleArea = NSClassFromString(@"IDEConsoleArea"); - Method originalMethod = class_getInstanceMethod([IDEConsoleArea class], @selector(_clearText)); - _clearText = method_getImplementation(originalMethod); - - IMP newImpl = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_clearText)); - method_setImplementation(originalMethod, newImpl); + MCLogger(@"self:%@\ntarget:%@", self, SearchField.consoleTextView.textStorage); + originalFixAttributesInRangeIMP(self, _cmd, range); + +// if (self == SearchField.consoleTextView.textStorage) { +// MCLogger(@"target textStorage! fix attr:%@", [self.string substringWithRange:range]); +// } +} + +@end + +/////////////////////////////////////////////////////////////////////////////////// +#pragma mark - MCIDEConsoleAdaptor +static IMP originalOutputForStandardOutputIMP = nil; + +@interface MCIDEConsoleAdaptor :NSObject +- (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedByUser:(BOOL)arg3; +@end + + +static const void *kUnProcessedOutputKey; +static const void *kTimerKey; + +@interface NSObject (MCIDEConsoleAdaptor) +- (void)setUnprocessedOutput:(NSString *)output; +- (NSString *)unprocessedOutput; + +- (void)setTimer:(NSTimer *)timer; +- (NSTimer *)timer; + +- (void)timerTimeout:(NSTimer *)timer; +@end + +@implementation NSObject (MCIDEConsoleAdaptor) + +- (void)setUnprocessedOutput:(NSString *)output +{ + objc_setAssociatedObject(self, &kUnProcessedOutputKey, output, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSString *)unprocessedOutput +{ + return objc_getAssociatedObject(self, &kUnProcessedOutputKey); +} + +- (void)setTimer:(NSTimer *)timer +{ + objc_setAssociatedObject(self, &kTimerKey, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSTimer *)timer +{ + return objc_getAssociatedObject(self, &kTimerKey); +} + +- (void)timerTimeout:(NSTimer *)timer +{ + if (self.unprocessedOutput.length > 0) { + NSArray *args = timer.userInfo; + originalOutputForStandardOutputIMP(self, _cmd, self.unprocessedOutput, [args[0] boolValue], [args[1] boolValue]); + } + self.unprocessedOutput = nil; +} + +@end + + +@implementation MCIDEConsoleAdaptor + +- (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedByUser:(BOOL)arg3 +{ + [self.timer invalidate]; + self.timer = nil; + + NSError *error; + static NSRegularExpression *LogSeperatorPattern = nil; + if (LogSeperatorPattern == nil) { + LogSeperatorPattern = [NSRegularExpression regularExpressionWithPattern:@"\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s\\S+\\[[\\da-fA-F]+\\:[\\da-fA-F]+\\]\\s" options:NSRegularExpressionCaseInsensitive error:&error]; + if (!LogSeperatorPattern) { + MCLogger(@"%@", error); + } + } + NSString *unprocessedstring = self.unprocessedOutput; + NSString *buffer = arg1; + if (unprocessedstring.length > 0) { + buffer = [unprocessedstring stringByAppendingString:arg1]; + self.unprocessedOutput = nil; + } + + if (LogSeperatorPattern) { + NSArray *matches = [LogSeperatorPattern matchesInString:buffer options:0 range:NSMakeRange(0, [buffer length])]; + if (matches.count > 0) { + NSRange lastMatchingRange = NSMakeRange(NSNotFound, 0); + for (NSTextCheckingResult *result in matches) { + + if (lastMatchingRange.location != NSNotFound) { + NSString *logItemData = [buffer substringWithRange:NSMakeRange(lastMatchingRange.location, result.range.location - lastMatchingRange.location)]; + originalOutputForStandardOutputIMP(self, _cmd, logItemData, arg2, arg3); + } + lastMatchingRange = result.range; + } + if (lastMatchingRange.location + lastMatchingRange.length < [buffer length]) { + self.unprocessedOutput = [buffer substringFromIndex:lastMatchingRange.location]; + } + } else { + originalOutputForStandardOutputIMP(self, _cmd, buffer, arg2, arg3); + } + } else { + originalOutputForStandardOutputIMP(self, _cmd, arg1, arg2, arg3); + } + + if (self.unprocessedOutput.length > 0) { + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(timerTimeout:) userInfo:@[ @(arg2), @(arg3) ] repeats:NO]; + } + +} + +@end + + +/////////////////////////////////////////////////////////////////////////////////////////// + +@interface MCLog () +{ + NSMutableDictionary *workspace; +} +@end + +@implementation MCLog + ++ (void)load +{ + NSLog(@"%s, env: %s", __PRETTY_FUNCTION__, getenv(MCLOG_FLAG)); + + if (getenv(MCLOG_FLAG) && !strcmp(getenv(MCLOG_FLAG), "YES")) { + // alreay installed plugin + return; + } + + hookDVTTextStorage(); + hookIDEConsoleAdaptor(); + hookIDEConsoleArea(); + hookIDEConsoleItem(); + + originConsoleItemsMap = [NSMutableDictionary dictionary]; + setenv(MCLOG_FLAG, "YES", 0); } ++ (void)pluginDidLoad:(NSBundle *)bundle +{ + NSLog(@"%s, %@", __PRETTY_FUNCTION__, bundle); + static id sharedPlugin = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedPlugin = [[self alloc] init]; + }); +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (id)init +{ + self = [super init]; + if (self) { + workspace = [NSMutableDictionary dictionary]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activate:) name:@"IDEIndexWillIndexWorkspaceNotification" object:nil]; + } + return self; +} + +- (NSView *)getViewByClassName:(NSString *)className andContainerView:(NSView *)container +{ + Class class = NSClassFromString(className); + for (NSView *subView in container.subviews) { + if ([subView isKindOfClass:class]) { + return subView; + } else { + NSView *view = [self getViewByClassName:className andContainerView:subView]; + if ([view isKindOfClass:class]) { + return view; + } + } + } + return nil; +} + +- (NSView *)getParantViewByClassName:(NSString *)className andView:(NSView *)view +{ + NSView *superView = view.superview; + while (superView) { + if ([[superView className] isEqualToString:className]) { + return superView; + } + superView = superView.superview; + } + + return nil; +} + +- (BOOL)addCustomViews +{ + NSView *contentView = [[NSApp mainWindow] contentView]; + NSView *consoleTextView = [self getViewByClassName:@"IDEConsoleTextView" andContainerView:contentView]; + if (!consoleTextView) { + return NO; + } + + contentView = [self getParantViewByClassName:@"DVTControllerContentView" andView:consoleTextView]; + NSView *scopeBarView = [self getViewByClassName:@"DVTScopeBarView" andContainerView:contentView]; + if (!scopeBarView) { + return NO; + } + + NSButton *button = nil; + NSPopUpButton *filterButton = nil; + for (NSView *subView in scopeBarView.subviews) { + if (button && filterButton) break; + if (button == nil && [[subView className] isEqualToString:@"NSButton"]) { + button = (NSButton *)subView; + } + else if (filterButton == nil && [[subView className] isEqualToString:@"NSPopUpButton"]) { + filterButton = (NSPopUpButton *)subView; + } + } + + if (!button) { + return NO; + } + + if(filterButton) { + [self filterPopupButton:filterButton addItemWithTitle:@"Verbose" tag:MCLogLevelVerbose]; + [self filterPopupButton:filterButton addItemWithTitle:@"Info" tag:MCLogLevelInfo]; + [self filterPopupButton:filterButton addItemWithTitle:@"Warn" tag:MCLogLevelWarn]; + [self filterPopupButton:filterButton addItemWithTitle:@"Error" tag:MCLogLevelError]; + } + + + if ([scopeBarView viewWithTag:kTagSearchField]) { + return YES; + } + + NSRect frame = button.frame; + frame.origin.x -= button.frame.size.width + 205; + frame.size.width = 200.0; + frame.size.height -= 2; + + NSSearchField *searchField = [[NSSearchField alloc] initWithFrame:frame]; + searchField.autoresizingMask = NSViewMinXMargin; + searchField.font = [NSFont systemFontOfSize:11.0]; + searchField.delegate = self; + searchField.consoleTextView = (NSTextView *)consoleTextView; + searchField.tag = kTagSearchField; + [searchField.cell setPlaceholderString:@"Regular Expression"]; + [scopeBarView addSubview:searchField]; + + SearchField = searchField; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(searchFieldDidEndEditing:) name:NSControlTextDidEndEditingNotification object:nil]; + + return YES; +} + +- (void)filterPopupButton:(NSPopUpButton *)popupButton addItemWithTitle:(NSString *)title tag:(NSUInteger)tag +{ + [popupButton addItemWithTitle:title]; + [popupButton itemAtIndex:popupButton.numberOfItems - 1].tag = tag; +} + +#pragma mark - Notifications + +- (void)searchFieldDidEndEditing:(NSNotification *)notification +{ + if (![[notification object] isMemberOfClass:[NSSearchField class]]) { + return; + } + + NSSearchField *searchField = [notification object]; + if (![searchField respondsToSelector:@selector(consoleTextView)]) { + return; + } + + if (![searchField respondsToSelector:@selector(consoleArea)]) { + return; + } + + NSTextView *consoleTextView = searchField.consoleTextView; + MCLogIDEConsoleArea *consoleArea = searchField.consoleArea; + + // get rid of the annoying 'undeclared selector' warning +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + if ([consoleTextView respondsToSelector:@selector(clearConsoleItems)]) { + [consoleTextView performSelector:@selector(clearConsoleItems) withObject:nil]; + } + + NSMutableDictionary *originConsoleItems = [originConsoleItemsMap objectForKey:hash(consoleArea)]; + NSArray *sortedItems = [[originConsoleItems allValues] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSTimeInterval a = [obj1 timestamp]; + NSTimeInterval b = [obj2 timestamp]; + if (a > b) { + return NSOrderedDescending; + } + + if(a < b) { + return NSOrderedAscending; + } + + return NSOrderedSame; + }]; + + + + if ([consoleArea respondsToSelector:@selector(_appendItems:)]) { + [consoleArea performSelector:@selector(_appendItems:) withObject:sortedItems]; + } +#pragma clang diagnostic pop +} + +- (void)activate:(NSNotification *)notification { + + id IDEIndex = [notification object]; + BOOL isAdded = [[workspace objectForKey:hash(IDEIndex)] boolValue]; + if (isAdded) { + return; + } + if ([self addCustomViews]) { + [workspace setObject:@(YES) forKey:hash(IDEIndex)]; + } +} + +@end + +#pragma mark - method hookers + +void hookIDEConsoleArea() +{ + Class IDEConsoleArea = NSClassFromString(@"IDEConsoleArea"); + //_shouldAppendItem + Method shouldAppendItem = class_getInstanceMethod(IDEConsoleArea, @selector(_shouldAppendItem:)); + IMP hookedShouldAppendItemIMP = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_shouldAppendItem:)); + method_setImplementation(shouldAppendItem, hookedShouldAppendItemIMP); + + //_clearText + Method clearText = class_getInstanceMethod(IDEConsoleArea, @selector(_clearText)); + originalClearTextIMP = method_getImplementation(clearText); + IMP newImpl = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_clearText)); + method_setImplementation(clearText, newImpl); +} + +void hookIDEConsoleItem() +{ + Class IDEConsoleItem = NSClassFromString(@"IDEConsoleItem"); + Method consoleItemInit = class_getInstanceMethod(IDEConsoleItem, @selector(initWithAdaptorType:content:kind:)); + IDEConsoleItemInitIMP = method_getImplementation(consoleItemInit); + IMP newConsoleItemInit = class_getMethodImplementation([MCIDEConsoleItem class], @selector(initWithAdaptorType:content:kind:)); + method_setImplementation(consoleItemInit, newConsoleItemInit); +} + +void hookDVTTextStorage() +{ + Class DVTTextStorage = NSClassFromString(@"DVTTextStorage"); + //appendAttributedString + Method appendAttributedString = class_getInstanceMethod(DVTTextStorage, @selector(appendAttributedString:)); + originalAppendAttributedStringIMP = method_getImplementation(appendAttributedString); + IMP newAppendAttributedStringIMP = class_getMethodImplementation([MCDVTTextStorage class], @selector(appendAttributedString:)); + method_setImplementation(appendAttributedString, newAppendAttributedStringIMP); + + Method fixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(fixAttributesInRange:)); + originalFixAttributesInRangeIMP = method_getImplementation(fixAttributesInRange); + IMP newFixAttributesInRangeIMP = class_getMethodImplementation([MCDVTTextStorage class], @selector(fixAttributesInRange:)); + method_setImplementation(fixAttributesInRange, newFixAttributesInRangeIMP); +} + +void hookIDEConsoleAdaptor() +{ + Class IDEConsoleAdaptor = NSClassFromString(@"IDEConsoleAdaptor"); + Method outputForStandardOutput = class_getInstanceMethod(IDEConsoleAdaptor, @selector(outputForStandardOutput:isPrompt:isOutputRequestedByUser:)); + originalOutputForStandardOutputIMP = method_getImplementation(outputForStandardOutput); + IMP newOutputForStandardOutputIMP = class_getMethodImplementation([MCIDEConsoleAdaptor class], @selector(outputForStandardOutput:isPrompt:isOutputRequestedByUser:)); + method_setImplementation(outputForStandardOutput, newOutputForStandardOutputIMP); +} + +#pragma mark - util methods NSSearchField *getSearchField(id consoleArea) { #pragma clang diagnostic push @@ -332,4 +705,26 @@ void replaceClearTextMethod() } return [NSString stringWithFormat:@"%lx", (long)obj]; +} + + +NSArray *backtraceStack() +{ + void* callstack[128]; + int frames = backtrace(callstack, 128); + char **symbols = backtrace_symbols(callstack, frames); + + int i; + NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; + for (i = 0; i < frames; ++i) { + NSString *line = [NSString stringWithUTF8String:symbols[i]]; + if (line == nil) { + break; + } + [backtrace addObject:line]; + } + + free(symbols); + + return backtrace; } \ No newline at end of file From 282db0e02d6f43242e0acc68a761573cbc42913c Mon Sep 17 00:00:00 2001 From: lixiaomin Date: Thu, 20 Nov 2014 17:02:07 +0800 Subject: [PATCH 2/6] fix log regular expression --- MCLog/MCLog.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MCLog/MCLog.m b/MCLog/MCLog.m index 0931a96..8daa029 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -127,7 +127,7 @@ - (void)updateItemAttribute:(id)item content = [NSString stringWithFormat:@"\\033[31m%@\\033[0m", content]; } else { NSString *originalContent = [ControlCharsPattern stringByReplacingMatchesInString:content options:0 range:NSMakeRange(0, content.length) withTemplate:@""]; - static NSString *patternString = @"\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+-\\[%@\\]\\s+.*"; + static NSString *patternString = @"^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+-\\[%@\\]\\s+.*"; static NSRegularExpression *VerboseLogPattern = nil; static NSRegularExpression *InfoLogPattern = nil; static NSRegularExpression *WarnLogPattern = nil; From aa21a5807200abff2909c58428136f67b801fcad Mon Sep 17 00:00:00 2001 From: lixiaomin Date: Tue, 25 Nov 2014 19:49:48 +0800 Subject: [PATCH 3/6] save tmp job --- MCLog/MCLog.h | 11 ++++ MCLog/MCLog.m | 135 ++++++++++++++++++++++---------------------------- 2 files changed, 69 insertions(+), 77 deletions(-) diff --git a/MCLog/MCLog.h b/MCLog/MCLog.h index c7565a1..2344e52 100644 --- a/MCLog/MCLog.h +++ b/MCLog/MCLog.h @@ -8,6 +8,17 @@ #import +#if TARGET_OS_IPHONE +#define LC_ESC @"\xC2\xA0" +#else +#define LC_ESC @"\033" +#endif + + + +// Reset colors +#define LC_RESET LC_ESC @"[0m" + @interface MCLog : NSObject + (void)pluginDidLoad:(NSBundle *)bundle; @end diff --git a/MCLog/MCLog.m b/MCLog/MCLog.m index 8daa029..0e75550 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -23,11 +23,13 @@ NSSearchField *getSearchField(id consoleArea); NSString *hash(id obj); + NSArray *backtraceStack(); void hookDVTTextStorage(); void hookIDEConsoleAdaptor(); void hookIDEConsoleArea(); void hookIDEConsoleItem(); +NSRegularExpression * logItemPrefixPattern(); typedef NS_ENUM(NSUInteger, MCLogLevel) { @@ -37,7 +39,6 @@ typedef NS_ENUM(NSUInteger, MCLogLevel) { MCLogLevelError }; - //////////////////////////////////////////////////////////////////////////////////// #pragma mark - NSSearchField (MCLog) @interface NSSearchField (MCLog) @@ -98,84 +99,52 @@ - (NSUInteger)logLevel return [objc_getAssociatedObject(self, &LogLevelAssociateKey) unsignedIntegerValue]; } + + - (void)updateItemAttribute:(id)item { NSError *error = nil; - static NSRegularExpression *TimePattern = nil; - if (TimePattern == nil) { - TimePattern = [NSRegularExpression regularExpressionWithPattern:@"^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.*" options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; - if (!TimePattern) { - MCLogger(@"%@", error); - } + NSString *logText = [item valueForKey:@"content"]; + if ([[item valueForKey:@"error"] boolValue]) { + [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), logText]; + return; } + if (![[item valueForKey:@"output"] boolValue] || [[item valueForKey:@"outputRequestedByUser"] boolValue]) { + return; + } + + NSRange prefixRange = [logItemPrefixPattern() rangeOfFirstMatchInString:logText options:0 range:NSMakeRange(0, logText.length)]; + if (prefixRange.location != 0 || logText.length <= prefixRange.length) { + return; + } + static NSRegularExpression *ControlCharsPattern = nil; if (ControlCharsPattern == nil) { - ControlCharsPattern = [NSRegularExpression regularExpressionWithPattern:@"\\\\0?33\\[[\\d;]+m" options:0 error:&error]; + ControlCharsPattern = [NSRegularExpression regularExpressionWithPattern:LC_ESC@"\\[[\\d;]+m" options:0 error:&error]; if (!ControlCharsPattern) { MCLogger(@"%@", error); } } + NSString *content = [logText substringFromIndex:prefixRange.length]; + NSString *originalContent = [ControlCharsPattern stringByReplacingMatchesInString:content options:0 range:NSMakeRange(0, content.length) withTemplate:@""]; - NSString *content = [item valueForKey:@"content"]; - if ([[item valueForKey:@"output"] boolValue] || [[item valueForKey:@"error"] boolValue]) { - if ([TimePattern matchesInString:content options:0 range:NSMakeRange(0, content.length)].count) { - //MCLogger(@"%@ matched pattern:'%@'", content, TimePattern); - //content = [content substringFromIndex:11]; - } - if ([[item valueForKey:@"error"] boolValue]) { - content = [NSString stringWithFormat:@"\\033[31m%@\\033[0m", content]; - } else { - NSString *originalContent = [ControlCharsPattern stringByReplacingMatchesInString:content options:0 range:NSMakeRange(0, content.length) withTemplate:@""]; - static NSString *patternString = @"^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+-\\[%@\\]\\s+.*"; - static NSRegularExpression *VerboseLogPattern = nil; - static NSRegularExpression *InfoLogPattern = nil; - static NSRegularExpression *WarnLogPattern = nil; - static NSRegularExpression *ErrorLogPattern = nil; - - if (!VerboseLogPattern) { - VerboseLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"VERBOSE"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; - if (!VerboseLogPattern) { - MCLogger(@"%@", error); - } - } - if (!InfoLogPattern) { - InfoLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"INFO"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; - if (!InfoLogPattern) { - MCLogger(@"%@", error); - } - } - if (!WarnLogPattern) { - WarnLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"WARN"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; - if (!WarnLogPattern) { - MCLogger(@"%@", error); - } - } - if (!ErrorLogPattern) { - ErrorLogPattern = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:patternString, @"ERROR"] options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators error:&error]; - if (!ErrorLogPattern) { - MCLogger(@"%@", error); - } - } - NSRange matchingRange = (NSRange){.location = 0, .length = originalContent.length}; - if ([VerboseLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { - [item setLogLevel:MCLogLevelVerbose]; - } - else if ([InfoLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { - [item setLogLevel:MCLogLevelInfo]; - } - else if ([WarnLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { - [item setLogLevel:MCLogLevelWarn]; - } - else if ([ErrorLogPattern rangeOfFirstMatchInString:originalContent options:0 range:matchingRange].length == matchingRange.length) { - [item setLogLevel:MCLogLevelError]; - } - } - } else { - //content = [@"\\033[0m" stringByAppendingString:content]; + if ([originalContent hasPrefix:@"-[VERBOSE]"]) { + [item setLogLevel:MCLogLevelVerbose]; + //content = [NSString stringWithFormat:@""] + } + else if ([originalContent hasPrefix:@"-[INFO]"]) { + [item setLogLevel:MCLogLevelInfo]; + //content = [NSString stringWithFormat:@""] + } + else if ([originalContent hasPrefix:@"-[WARN]"]) { + [item setLogLevel:MCLogLevelWarn]; + //content = [NSString stringWithFormat:@""] + } + else if ([originalContent hasPrefix:@"-[ERROR]"]) { + [item setLogLevel:MCLogLevelError]; + //content = [NSString stringWithFormat:@""] } - - [item setValue:content forKey:@"content"]; } @end @@ -192,7 +161,7 @@ - (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3 { id item = IDEConsoleItemInitIMP(self, _cmd, arg1, arg2, arg3); [self updateItemAttribute:item]; - MCLogger(@"log level:%zd", [item logLevel]); + MCLogger(@"%@,\nlog level:%zd", item, [item logLevel]); return item; } @@ -373,14 +342,8 @@ - (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedBy [self.timer invalidate]; self.timer = nil; - NSError *error; - static NSRegularExpression *LogSeperatorPattern = nil; - if (LogSeperatorPattern == nil) { - LogSeperatorPattern = [NSRegularExpression regularExpressionWithPattern:@"\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s\\S+\\[[\\da-fA-F]+\\:[\\da-fA-F]+\\]\\s" options:NSRegularExpressionCaseInsensitive error:&error]; - if (!LogSeperatorPattern) { - MCLogger(@"%@", error); - } - } + NSRegularExpression *logSeperatorPattern = logItemPrefixPattern(); + NSString *unprocessedstring = self.unprocessedOutput; NSString *buffer = arg1; if (unprocessedstring.length > 0) { @@ -388,14 +351,16 @@ - (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedBy self.unprocessedOutput = nil; } - if (LogSeperatorPattern) { - NSArray *matches = [LogSeperatorPattern matchesInString:buffer options:0 range:NSMakeRange(0, [buffer length])]; + if (logSeperatorPattern) { + NSArray *matches = [logSeperatorPattern matchesInString:buffer options:0 range:NSMakeRange(0, [buffer length])]; + MCLogger(@"matchs: %@", matches); if (matches.count > 0) { NSRange lastMatchingRange = NSMakeRange(NSNotFound, 0); for (NSTextCheckingResult *result in matches) { if (lastMatchingRange.location != NSNotFound) { NSString *logItemData = [buffer substringWithRange:NSMakeRange(lastMatchingRange.location, result.range.location - lastMatchingRange.location)]; + MCLogger(@"item: %@", logItemData); originalOutputForStandardOutputIMP(self, _cmd, logItemData, arg2, arg3); } lastMatchingRange = result.range; @@ -685,6 +650,22 @@ void hookIDEConsoleAdaptor() } #pragma mark - util methods + +NSRegularExpression * logItemPrefixPattern() +{ + static NSRegularExpression *pattern = nil; + if (pattern == nil) { + NSError *error = nil; + pattern = [NSRegularExpression regularExpressionWithPattern:@"\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+" + options:NSRegularExpressionCaseInsensitive + error:&error]; + if (!pattern) { + MCLogger(@"%@", error); + } + } + return pattern; +} + NSSearchField *getSearchField(id consoleArea) { #pragma clang diagnostic push From 561bb1251084eba174ac26293d3e3f60410bcf33 Mon Sep 17 00:00:00 2001 From: lixiaomin Date: Wed, 26 Nov 2014 20:14:29 +0800 Subject: [PATCH 4/6] save point --- MCLog/MCLog.m | 163 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 56 deletions(-) diff --git a/MCLog/MCLog.m b/MCLog/MCLog.m index 0e75550..ba3b074 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -30,6 +30,7 @@ void hookIDEConsoleArea(); void hookIDEConsoleItem(); NSRegularExpression * logItemPrefixPattern(); +NSRegularExpression * escCharPattern(); typedef NS_ENUM(NSUInteger, MCLogLevel) { @@ -131,19 +132,19 @@ - (void)updateItemAttribute:(id)item if ([originalContent hasPrefix:@"-[VERBOSE]"]) { [item setLogLevel:MCLogLevelVerbose]; - //content = [NSString stringWithFormat:@""] + content = [NSString stringWithFormat:(LC_ESC @"[34m%@" LC_RESET), content]; } else if ([originalContent hasPrefix:@"-[INFO]"]) { [item setLogLevel:MCLogLevelInfo]; - //content = [NSString stringWithFormat:@""] + content = [NSString stringWithFormat:(LC_ESC @"[32m%@" LC_RESET), content]; } else if ([originalContent hasPrefix:@"-[WARN]"]) { [item setLogLevel:MCLogLevelWarn]; - //content = [NSString stringWithFormat:@""] + content = [NSString stringWithFormat:(LC_ESC @"[33m%@" LC_RESET), content]; } else if ([originalContent hasPrefix:@"-[ERROR]"]) { [item setLogLevel:MCLogLevelError]; - //content = [NSString stringWithFormat:@""] + content = [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), content]; } } @@ -161,7 +162,7 @@ - (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3 { id item = IDEConsoleItemInitIMP(self, _cmd, arg1, arg2, arg3); [self updateItemAttribute:item]; - MCLogger(@"%@,\nlog level:%zd", item, [item logLevel]); + MCLogger(@"%@, logLevel:%zd, adaptorType:%@", item, [item logLevel], [item valueForKey:@"adaptorType"]); return item; } @@ -171,49 +172,56 @@ - (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3 /////////////////////////////////////////////////////////////////////////////////// #pragma mark - MCLogIDEConsoleArea - +static IMP OriginalShouldAppendItem = nil; @interface MCLogIDEConsoleArea : NSViewController - (BOOL)_shouldAppendItem:(id)obj; - (void)_clearText; @end -static IMP originalClearTextIMP = nil; +static IMP OriginalClearTextIMP = nil; @implementation MCLogIDEConsoleArea - (BOOL)_shouldAppendItem:(id)obj; { - MCLogger(@"should append item:[%@]\n%@\nadaptorType:%@; kind:%zd", [obj class], obj, [obj valueForKey:@"adaptorType"], [obj valueForKey:@"kind"]); - NSSearchField *searchField = getSearchField(self); + if (!searchField.consoleArea) { + searchField.consoleArea = self; + } + + NSMutableDictionary *originConsoleItems = [originConsoleItemsMap objectForKey:hash(self)]; + if (!originConsoleItems) { + originConsoleItems = [NSMutableDictionary dictionary]; + } + + NSInteger filterMode = [[self valueForKey:@"filterMode"] intValue]; + BOOL shouldShowLogLevel = YES; + if (filterMode >= MCLogLevelVerbose) { + shouldShowLogLevel = [obj logLevel] >= filterMode + || [[obj valueForKey:@"input"] boolValue] + || [[obj valueForKey:@"prompt"] boolValue] + || [[obj valueForKey:@"outputRequestedByUser"] boolValue] + || [[obj valueForKey:@"adaptorType"] hasSuffix:@".Debugger"]; + } else { + shouldShowLogLevel = [OriginalShouldAppendItem(self, _cmd, obj) boolValue]; + } + + if (!shouldShowLogLevel) { + if (searchField) { + // store all console items. + [originConsoleItems setObject:obj forKey:@([obj timestamp])]; + [originConsoleItemsMap setObject:originConsoleItems forKey:hash(self)]; + } + return NO; + } + if (!searchField) { return YES; } - - if (!searchField.consoleArea) { - searchField.consoleArea = self; - } - - NSMutableDictionary *originConsoleItems = [originConsoleItemsMap objectForKey:hash(self)]; - if (!originConsoleItems) { - originConsoleItems = [NSMutableDictionary dictionary]; - } + -// if (originConsoleItems[@([obj timestamp])] == nil) { -// [MCLogIDEConsoleArea updateItemAttribute:obj]; -// } - // store all console items. [originConsoleItems setObject:obj forKey:@([obj timestamp])]; [originConsoleItemsMap setObject:originConsoleItems forKey:hash(self)]; - - if (![searchField.stringValue length]) { - NSInteger filterMode = [[self valueForKey:@"filterMode"] intValue]; - if (filterMode >= MCLogLevelVerbose) { - MCLogger(@"log level:%zd; filter mode:%zd", [obj logLevel], filterMode); - return [obj logLevel] >= filterMode; - } - return YES; - } // test with the regular expression NSString *content = [obj content]; @@ -229,7 +237,11 @@ - (BOOL)_shouldAppendItem:(id)obj; } NSArray *matches = [regex matchesInString:content options:0 range:range]; - if ([matches count]) { + if ([matches count] > 0 + || [[obj valueForKey:@"input"] boolValue] + || [[obj valueForKey:@"prompt"] boolValue] + || [[obj valueForKey:@"outputRequestedByUser"] boolValue] + || [[obj valueForKey:@"adaptorType"] hasSuffix:@".Debugger"]) { return YES; } @@ -238,7 +250,7 @@ - (BOOL)_shouldAppendItem:(id)obj; - (void)_clearText { - originalClearTextIMP(self, _cmd); + OriginalClearTextIMP(self, _cmd); [originConsoleItemsMap removeObjectForKey:hash(self)]; } @end @@ -248,33 +260,56 @@ - (void)_clearText /////////////////////////////////////////////////////////////////////////////////// #pragma mark - MCDVTTextStorage -static IMP originalAppendAttributedStringIMP = nil; -static IMP originalFixAttributesInRangeIMP = nil; +static IMP OriginalFixAttributesInRangeIMP = nil; +static void *kLastAttributeKey; @interface MCDVTTextStorage : NSTextStorage - (void)fixAttributesInRange:(NSRange)range; -- (void)appendAttributedString:(NSAttributedString *)attrString; +- (void)setLastAttribute:(NSDictionary *)attribute; +- (NSDictionary *)lastAttribute; @end @implementation MCDVTTextStorage +- (void)setLastAttribute:(NSDictionary *)attribute +{ + objc_setAssociatedObject(self, &kLastAttributeKey, attribute, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} -- (void)appendAttributedString:(NSAttributedString *)attrString +- (NSDictionary *)lastAttribute { - MCLogger(@"self:%@\ntarget:%@", self, SearchField.consoleTextView.textStorage); - originalAppendAttributedStringIMP(self, _cmd, attrString); -// if (self == SearchField.consoleTextView.textStorage) { -// MCLogger(@"target textStorage! append attrString:%@", attrString); -// } + return objc_getAssociatedObject(self, &kLastAttributeKey); } - (void)fixAttributesInRange:(NSRange)range { - MCLogger(@"self:%@\ntarget:%@", self, SearchField.consoleTextView.textStorage); - originalFixAttributesInRangeIMP(self, _cmd, range); + OriginalFixAttributesInRangeIMP(self, _cmd, range); + + NSRange escRange = [self.string rangeOfString:LC_ESC options:0 range:range]; + if (escRange.location == NSNotFound) { + return; + } -// if (self == SearchField.consoleTextView.textStorage) { -// MCLogger(@"target textStorage! fix attr:%@", [self.string substringWithRange:range]); + __block NSRange lastRange = NSMakeRange(NSNotFound, 0); + [escCharPattern() enumerateMatchesInString:self.string options:0 range:range usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + + }]; + +// NSArray *matches = [escCharPattern() matchesInString:self.string options:0 range:range]; +// if (matches.count == 0) { +// return; +// } +// +// for (NSUInteger i = 0; i < matches.count; ++i) { +// NSTextCheckingResult *result = matches[i]; +// if (i == 0 && result.range.location > range.location && self.lastAttribute) { +// [self addAttributes:self.lastAttribute range:NSMakeRange(range.location, result.range.location - range.location)]; +// self.lastAttribute = nil; +// } +// else { +// +// } // } + } @end @@ -353,14 +388,12 @@ - (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedBy if (logSeperatorPattern) { NSArray *matches = [logSeperatorPattern matchesInString:buffer options:0 range:NSMakeRange(0, [buffer length])]; - MCLogger(@"matchs: %@", matches); if (matches.count > 0) { NSRange lastMatchingRange = NSMakeRange(NSNotFound, 0); for (NSTextCheckingResult *result in matches) { if (lastMatchingRange.location != NSNotFound) { NSString *logItemData = [buffer substringWithRange:NSMakeRange(lastMatchingRange.location, result.range.location - lastMatchingRange.location)]; - MCLogger(@"item: %@", logItemData); originalOutputForStandardOutputIMP(self, _cmd, logItemData, arg2, arg3); } lastMatchingRange = result.range; @@ -503,6 +536,10 @@ - (BOOL)addCustomViews [self filterPopupButton:filterButton addItemWithTitle:@"Error" tag:MCLogLevelError]; } + NSInteger selectedItem = [filterButton indexOfItemWithTag:[[consoleTextView valueForKey:@"logMode"] intValue]]; + if (selectedItem < 0 || selectedItem >= [filterButton numberOfItems]) { + [filterButton selectItemAtIndex:0]; + } if ([scopeBarView viewWithTag:kTagSearchField]) { return YES; @@ -606,12 +643,13 @@ void hookIDEConsoleArea() Class IDEConsoleArea = NSClassFromString(@"IDEConsoleArea"); //_shouldAppendItem Method shouldAppendItem = class_getInstanceMethod(IDEConsoleArea, @selector(_shouldAppendItem:)); + OriginalShouldAppendItem = method_getImplementation(shouldAppendItem); IMP hookedShouldAppendItemIMP = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_shouldAppendItem:)); method_setImplementation(shouldAppendItem, hookedShouldAppendItemIMP); //_clearText Method clearText = class_getInstanceMethod(IDEConsoleArea, @selector(_clearText)); - originalClearTextIMP = method_getImplementation(clearText); + OriginalClearTextIMP = method_getImplementation(clearText); IMP newImpl = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_clearText)); method_setImplementation(clearText, newImpl); } @@ -628,14 +666,14 @@ void hookIDEConsoleItem() void hookDVTTextStorage() { Class DVTTextStorage = NSClassFromString(@"DVTTextStorage"); - //appendAttributedString - Method appendAttributedString = class_getInstanceMethod(DVTTextStorage, @selector(appendAttributedString:)); - originalAppendAttributedStringIMP = method_getImplementation(appendAttributedString); - IMP newAppendAttributedStringIMP = class_getMethodImplementation([MCDVTTextStorage class], @selector(appendAttributedString:)); - method_setImplementation(appendAttributedString, newAppendAttributedStringIMP); +// //appendAttributedString +// Method appendAttributedString = class_getInstanceMethod(DVTTextStorage, @selector(appendAttributedString:)); +// originalAppendAttributedStringIMP = method_getImplementation(appendAttributedString); +// IMP newAppendAttributedStringIMP = class_getMethodImplementation([MCDVTTextStorage class], @selector(appendAttributedString:)); +// method_setImplementation(appendAttributedString, newAppendAttributedStringIMP); Method fixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(fixAttributesInRange:)); - originalFixAttributesInRangeIMP = method_getImplementation(fixAttributesInRange); + OriginalFixAttributesInRangeIMP = method_getImplementation(fixAttributesInRange); IMP newFixAttributesInRangeIMP = class_getMethodImplementation([MCDVTTextStorage class], @selector(fixAttributesInRange:)); method_setImplementation(fixAttributesInRange, newFixAttributesInRangeIMP); } @@ -656,7 +694,7 @@ void hookIDEConsoleAdaptor() static NSRegularExpression *pattern = nil; if (pattern == nil) { NSError *error = nil; - pattern = [NSRegularExpression regularExpressionWithPattern:@"\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+" + pattern = [NSRegularExpression regularExpressionWithPattern:@"\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\[.:]\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+" options:NSRegularExpressionCaseInsensitive error:&error]; if (!pattern) { @@ -666,6 +704,19 @@ void hookIDEConsoleAdaptor() return pattern; } +NSRegularExpression * escCharPattern() +{ + static NSRegularExpression *pattern = nil; + if (pattern == nil) { + NSError *error = nil; + pattern = [NSRegularExpression regularExpressionWithPattern:(LC_ESC @"\\[([\\d;]*\\d+)m") options:0 error:&error]; + if (!pattern) { + MCLogger(@"%@", error); + } + } + return pattern; +} + NSSearchField *getSearchField(id consoleArea) { #pragma clang diagnostic push From 07ca078920a875b867199a9b8d11c7cc1d22282b Mon Sep 17 00:00:00 2001 From: lixiaomin Date: Thu, 27 Nov 2014 16:14:07 +0800 Subject: [PATCH 5/6] finished feature: color output --- MCLog/MCLog.m | 174 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 34 deletions(-) diff --git a/MCLog/MCLog.m b/MCLog/MCLog.m index ba3b074..e651c90 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -15,6 +15,8 @@ #define MCLogger(fmt, ...) NSLog((@"[MCLog] %s(Line:%d) " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) +#define NSColorWithHexRGB(rgb) [NSColor colorWithCalibratedRed:((rgb) >> 16 & 0xFF) / 255.f green:((rgb) >> 8 & 0xFF) / 255.f blue:((rgb) & 0xFF) / 255.f alpha:1.f] + @class MCLogIDEConsoleArea; static NSMutableDictionary *originConsoleItemsMap; @@ -146,6 +148,8 @@ - (void)updateItemAttribute:(id)item [item setLogLevel:MCLogLevelError]; content = [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), content]; } + + [item setValue:[[logText substringWithRange:prefixRange] stringByAppendingString:content] forKey:@"content"]; } @end @@ -265,11 +269,58 @@ - (void)_clearText static void *kLastAttributeKey; @interface MCDVTTextStorage : NSTextStorage - (void)fixAttributesInRange:(NSRange)range; +@end + +@interface NSObject (DVTTextStorage) - (void)setLastAttribute:(NSDictionary *)attribute; - (NSDictionary *)lastAttribute; +- (void)updateAttributes:(NSMutableDictionary *)attrs withANSIESCString:(NSString *)ansiEscString; @end @implementation MCDVTTextStorage + +- (void)fixAttributesInRange:(NSRange)range +{ + OriginalFixAttributesInRangeIMP(self, _cmd, range); + + __block NSRange lastRange = NSMakeRange(range.location, 0); + NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; + if (self.lastAttribute.count > 0) { + [attrs setValuesForKeysWithDictionary:self.lastAttribute]; + } + + [escCharPattern() enumerateMatchesInString:self.string options:0 range:range usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + if (attrs.count > 0) { + NSRange attrRange = NSMakeRange(lastRange.location, result.range.location - lastRange.location); + [self addAttributes:attrs range:attrRange]; + MCLogger(@"apply attributes:%@\nin range:[%zd, %zd], affected string:%@", attrs, attrRange.location, attrRange.length, [self.string substringWithRange:attrRange]); + } + + NSString *attrsDesc = [self.string substringWithRange:[result rangeAtIndex:1]]; + if (attrsDesc.length == 0) { + [self addAttributes:@{ + NSFontAttributeName: [NSFont systemFontOfSize:0.000001f], + NSForegroundColorAttributeName: [NSColor clearColor] + } + range:result.range]; + lastRange = result.range; + return; + } + [self updateAttributes:attrs withANSIESCString:attrsDesc]; + [self addAttributes:@{ + NSFontAttributeName: [NSFont systemFontOfSize:0.000001f], + NSForegroundColorAttributeName: [NSColor clearColor] + } + range:result.range]; + lastRange = result.range; + }]; + self.lastAttribute = attrs; +} + +@end + +@implementation NSObject (DVTTextStorage) + - (void)setLastAttribute:(NSDictionary *)attribute { objc_setAssociatedObject(self, &kLastAttributeKey, attribute, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -280,36 +331,96 @@ - (NSDictionary *)lastAttribute return objc_getAssociatedObject(self, &kLastAttributeKey); } -- (void)fixAttributesInRange:(NSRange)range +- (void)updateAttributes:(NSMutableDictionary *)attrs withANSIESCString:(NSString *)ansiEscString { - OriginalFixAttributesInRangeIMP(self, _cmd, range); - - NSRange escRange = [self.string rangeOfString:LC_ESC options:0 range:range]; - if (escRange.location == NSNotFound) { - return; + NSArray *attrComponents = [ansiEscString componentsSeparatedByString:@";"]; + for (NSString *attrName in attrComponents) { + NSUInteger attrCode = [attrName integerValue]; + switch (attrCode) { + case 0: + [attrs removeAllObjects]; + break; + + case 1: + [attrs setObject:[NSFont boldSystemFontOfSize:11.f] forKey:NSFontAttributeName]; + break; + + case 4: + [attrs setObject:@( NSUnderlineStyleSingle ) forKey:NSUnderlineStyleAttributeName]; + break; + + case 24: + [attrs setObject:@(NSUnderlineStyleNone ) forKey:NSUnderlineStyleAttributeName]; + break; + //foreground color + case 30: //black + [attrs setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName]; + break; + + case 31: // Red + [attrs setObject:NSColorWithHexRGB(0xd70000) forKey:NSForegroundColorAttributeName]; + break; + + case 32: // Green + [attrs setObject:NSColorWithHexRGB(0x00ff00) forKey:NSForegroundColorAttributeName]; + break; + + case 33: // Yellow + [attrs setObject:NSColorWithHexRGB(0xffff00) forKey:NSForegroundColorAttributeName]; + break; + + case 34: // Blue + [attrs setObject:NSColorWithHexRGB(0x005fff) forKey:NSForegroundColorAttributeName]; + break; + + case 35: // purple + [attrs setObject:NSColorWithHexRGB(0xff00ff) forKey:NSForegroundColorAttributeName]; + break; + + case 36: // cyan + [attrs setObject:NSColorWithHexRGB(0x00ffff) forKey:NSForegroundColorAttributeName]; + break; + + case 37: // gray + [attrs setObject:NSColorWithHexRGB(0x808080) forKey:NSForegroundColorAttributeName]; + break; + //background color + case 40: //black + [attrs setObject:[NSColor blackColor] forKey:NSBackgroundColorAttributeName]; + break; + + case 41: // Red + [attrs setObject:NSColorWithHexRGB(0xd70000) forKey:NSBackgroundColorAttributeName]; + break; + + case 42: // Green + [attrs setObject:NSColorWithHexRGB(0x00ff00) forKey:NSBackgroundColorAttributeName]; + break; + + case 43: // Yellow + [attrs setObject:NSColorWithHexRGB(0xffff00) forKey:NSBackgroundColorAttributeName]; + break; + + case 44: // Blue + [attrs setObject:NSColorWithHexRGB(0x005fff) forKey:NSBackgroundColorAttributeName]; + break; + + case 45: // purple + [attrs setObject:NSColorWithHexRGB(0xff00ff) forKey:NSBackgroundColorAttributeName]; + break; + + case 46: // cyan + [attrs setObject:NSColorWithHexRGB(0x00ffff) forKey:NSBackgroundColorAttributeName]; + break; + + case 47: // gray + [attrs setObject:NSColorWithHexRGB(0x808080) forKey:NSBackgroundColorAttributeName]; + break; + + default: + break; + } } - - __block NSRange lastRange = NSMakeRange(NSNotFound, 0); - [escCharPattern() enumerateMatchesInString:self.string options:0 range:range usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { - - }]; - -// NSArray *matches = [escCharPattern() matchesInString:self.string options:0 range:range]; -// if (matches.count == 0) { -// return; -// } -// -// for (NSUInteger i = 0; i < matches.count; ++i) { -// NSTextCheckingResult *result = matches[i]; -// if (i == 0 && result.range.location > range.location && self.lastAttribute) { -// [self addAttributes:self.lastAttribute range:NSMakeRange(range.location, result.range.location - range.location)]; -// self.lastAttribute = nil; -// } -// else { -// -// } -// } - } @end @@ -666,11 +777,6 @@ void hookIDEConsoleItem() void hookDVTTextStorage() { Class DVTTextStorage = NSClassFromString(@"DVTTextStorage"); -// //appendAttributedString -// Method appendAttributedString = class_getInstanceMethod(DVTTextStorage, @selector(appendAttributedString:)); -// originalAppendAttributedStringIMP = method_getImplementation(appendAttributedString); -// IMP newAppendAttributedStringIMP = class_getMethodImplementation([MCDVTTextStorage class], @selector(appendAttributedString:)); -// method_setImplementation(appendAttributedString, newAppendAttributedStringIMP); Method fixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(fixAttributesInRange:)); OriginalFixAttributesInRangeIMP = method_getImplementation(fixAttributesInRange); @@ -694,7 +800,7 @@ void hookIDEConsoleAdaptor() static NSRegularExpression *pattern = nil; if (pattern == nil) { NSError *error = nil; - pattern = [NSRegularExpression regularExpressionWithPattern:@"\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\[.:]\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+" + pattern = [NSRegularExpression regularExpressionWithPattern:@"\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}[\\.:]\\d{3}\\s+.+\\[[\\da-fA-F]+:[\\da-fA-F]+\\]\\s+" options:NSRegularExpressionCaseInsensitive error:&error]; if (!pattern) { From fccbe7e418cd3a596b058e1bd6857add14aa96bf Mon Sep 17 00:00:00 2001 From: lixiaomin Date: Thu, 27 Nov 2014 17:29:14 +0800 Subject: [PATCH 6/6] disable debug log --- MCLog/MCLog.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MCLog/MCLog.m b/MCLog/MCLog.m index e651c90..cb75efd 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -166,7 +166,7 @@ - (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3 { id item = IDEConsoleItemInitIMP(self, _cmd, arg1, arg2, arg3); [self updateItemAttribute:item]; - MCLogger(@"%@, logLevel:%zd, adaptorType:%@", item, [item logLevel], [item valueForKey:@"adaptorType"]); + //MCLogger(@"%@, logLevel:%zd, adaptorType:%@", item, [item logLevel], [item valueForKey:@"adaptorType"]); return item; } @@ -293,7 +293,7 @@ - (void)fixAttributesInRange:(NSRange)range if (attrs.count > 0) { NSRange attrRange = NSMakeRange(lastRange.location, result.range.location - lastRange.location); [self addAttributes:attrs range:attrRange]; - MCLogger(@"apply attributes:%@\nin range:[%zd, %zd], affected string:%@", attrs, attrRange.location, attrRange.length, [self.string substringWithRange:attrRange]); + //MCLogger(@"apply attributes:%@\nin range:[%zd, %zd], affected string:%@", attrs, attrRange.location, attrRange.length, [self.string substringWithRange:attrRange]); } NSString *attrsDesc = [self.string substringWithRange:[result rangeAtIndex:1]];