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..2344e52 100644 --- a/MCLog/MCLog.h +++ b/MCLog/MCLog.h @@ -8,23 +8,19 @@ #import -@class MCLogIDEConsoleArea; +#if TARGET_OS_IPHONE +#define LC_ESC @"\xC2\xA0" +#else +#define LC_ESC @"\033" +#endif -@interface NSSearchField (MCLog) -@property (nonatomic, strong) MCLogIDEConsoleArea *consoleArea; -@property (nonatomic, strong) NSTextView *consoleTextView; -@end -@interface MCLogIDEConsoleArea : NSViewController -- (BOOL)_shouldAppendItem:(id)obj; -- (void)_clearText; -@end + +// Reset colors +#define LC_RESET LC_ESC @"[0m" @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..cb75efd 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -8,17 +8,531 @@ #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__) + +#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; static MCLogIDEConsoleArea *consoleArea = nil; -static IMP _clearText = nil; +static NSSearchField *SearchField = nil; + +NSSearchField *getSearchField(id consoleArea); +NSString *hash(id obj); + +NSArray *backtraceStack(); +void hookDVTTextStorage(); +void hookIDEConsoleAdaptor(); +void hookIDEConsoleArea(); +void hookIDEConsoleItem(); +NSRegularExpression * logItemPrefixPattern(); +NSRegularExpression * escCharPattern(); + + +typedef NS_ENUM(NSUInteger, MCLogLevel) { + MCLogLevelVerbose = 0x1000, + MCLogLevelInfo, + MCLogLevelWarn, + MCLogLevelError +}; + +//////////////////////////////////////////////////////////////////////////////////// +#pragma mark - NSSearchField (MCLog) +@interface NSSearchField (MCLog) +@property (nonatomic, strong) MCLogIDEConsoleArea *consoleArea; +@property (nonatomic, strong) NSTextView *consoleTextView; +@end + +static const void *kMCLogConsoleTextViewKey; +static const void *kMCLogIDEConsoleAreaKey; + +@implementation NSSearchField (MCLog) + +- (void)setConsoleArea:(MCLogIDEConsoleArea *)consoleArea +{ + objc_setAssociatedObject(self, &kMCLogIDEConsoleAreaKey, consoleArea, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (MCLogIDEConsoleArea *)consoleArea +{ + return objc_getAssociatedObject(self, &kMCLogIDEConsoleAreaKey); +} + +- (void)setConsoleTextView:(NSTextView *)consoleTextView +{ + objc_setAssociatedObject(self, &kMCLogConsoleTextViewKey, consoleTextView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSTextView *)consoleTextView +{ + return objc_getAssociatedObject(self, &kMCLogConsoleTextViewKey); +} + +@dynamic consoleArea; +@dynamic consoleTextView; +@end + + +/////////////////////////////////////////////////////////////////////////////////// +#pragma mark - MCIDEConsoleItem + +@interface NSObject (MCIDEConsoleItem) +- (void)setLogLevel:(NSUInteger)loglevel; +- (NSUInteger)logLevel; + +- (void)updateItemAttribute:(id)item; +@end + +static const void *LogLevelAssociateKey; +@implementation NSObject (MCIDEConsoleItem) + +- (void)setLogLevel:(NSUInteger)loglevel +{ + objc_setAssociatedObject(self, &LogLevelAssociateKey, @(loglevel), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSUInteger)logLevel +{ + return [objc_getAssociatedObject(self, &LogLevelAssociateKey) unsignedIntegerValue]; +} + + + +- (void)updateItemAttribute:(id)item +{ + NSError *error = nil; + 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: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:@""]; + + if ([originalContent hasPrefix:@"-[VERBOSE]"]) { + [item setLogLevel:MCLogLevelVerbose]; + content = [NSString stringWithFormat:(LC_ESC @"[34m%@" LC_RESET), content]; + } + else if ([originalContent hasPrefix:@"-[INFO]"]) { + [item setLogLevel:MCLogLevelInfo]; + content = [NSString stringWithFormat:(LC_ESC @"[32m%@" LC_RESET), content]; + } + else if ([originalContent hasPrefix:@"-[WARN]"]) { + [item setLogLevel:MCLogLevelWarn]; + content = [NSString stringWithFormat:(LC_ESC @"[33m%@" LC_RESET), content]; + } + else if ([originalContent hasPrefix:@"-[ERROR]"]) { + [item setLogLevel:MCLogLevelError]; + content = [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), content]; + } + + [item setValue:[[logText substringWithRange:prefixRange] stringByAppendingString:content] forKey:@"content"]; +} + +@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 +{ + id item = IDEConsoleItemInitIMP(self, _cmd, arg1, arg2, arg3); + [self updateItemAttribute:item]; + //MCLogger(@"%@, logLevel:%zd, adaptorType:%@", item, [item logLevel], [item valueForKey:@"adaptorType"]); + return item; +} + + +@end + + +/////////////////////////////////////////////////////////////////////////////////// +#pragma mark - MCLogIDEConsoleArea +static IMP OriginalShouldAppendItem = nil; +@interface MCLogIDEConsoleArea : NSViewController +- (BOOL)_shouldAppendItem:(id)obj; +- (void)_clearText; +@end + +static IMP OriginalClearTextIMP = nil; +@implementation MCLogIDEConsoleArea + +- (BOOL)_shouldAppendItem:(id)obj; +{ + 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; + } + + + // store all console items. + [originConsoleItems setObject:obj forKey:@([obj timestamp])]; + [originConsoleItemsMap setObject:originConsoleItems forKey:hash(self)]; + + // test with the regular expression + NSString *content = [obj content]; + NSRange range = NSMakeRange(0, content.length); + NSError *error; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:searchField.stringValue + options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators) + error:&error]; + if (regex == nil) { + // display all if with regex is error + NSLog(@"%s, error:%@", __PRETTY_FUNCTION__, error); + return YES; + } + + NSArray *matches = [regex matchesInString:content options:0 range:range]; + if ([matches count] > 0 + || [[obj valueForKey:@"input"] boolValue] + || [[obj valueForKey:@"prompt"] boolValue] + || [[obj valueForKey:@"outputRequestedByUser"] boolValue] + || [[obj valueForKey:@"adaptorType"] hasSuffix:@".Debugger"]) { + return YES; + } + + return NO; +} + +- (void)_clearText +{ + OriginalClearTextIMP(self, _cmd); + [originConsoleItemsMap removeObjectForKey:hash(self)]; +} +@end + + + + +/////////////////////////////////////////////////////////////////////////////////// +#pragma mark - MCDVTTextStorage +static IMP OriginalFixAttributesInRangeIMP = nil; + +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); +} + +- (NSDictionary *)lastAttribute +{ + return objc_getAssociatedObject(self, &kLastAttributeKey); +} + +- (void)updateAttributes:(NSMutableDictionary *)attrs withANSIESCString:(NSString *)ansiEscString +{ + 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; + } + } +} + +@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; + + NSRegularExpression *logSeperatorPattern = logItemPrefixPattern(); + + 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; + NSMutableDictionary *workspace; } @end @@ -26,23 +540,26 @@ @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; + 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); + + 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; + NSLog(@"%s, %@", __PRETTY_FUNCTION__, bundle); + static id sharedPlugin = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedPlugin = [[self alloc] init]; @@ -51,14 +568,14 @@ + (void)pluginDidLoad:(NSBundle *)bundle - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (id)init { self = [super init]; if (self) { - workspace = [NSMutableDictionary dictionary]; + workspace = [NSMutableDictionary dictionary]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activate:) name:@"IDEIndexWillIndexWorkspaceNotification" object:nil]; } return self; @@ -66,8 +583,8 @@ - (id)init - (NSView *)getViewByClassName:(NSString *)className andContainerView:(NSView *)container { - Class class = NSClassFromString(className); - for (NSView *subView in container.subviews) { + Class class = NSClassFromString(className); + for (NSView *subView in container.subviews) { if ([subView isKindOfClass:class]) { return subView; } else { @@ -82,234 +599,228 @@ - (NSView *)getViewByClassName:(NSString *)className andContainerView:(NSView *) - (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; + 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; - 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; + 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]; + } + + NSInteger selectedItem = [filterButton indexOfItemWithTag:[[consoleTextView valueForKey:@"logMode"] intValue]]; + if (selectedItem < 0 || selectedItem >= [filterButton numberOfItems]) { + [filterButton selectItemAtIndex:0]; + } + + 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 + 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)]; - } + + 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 - NSSearchField (MCLog) +#pragma mark - method hookers -static const void *kMCLogConsoleTextViewKey; -static const void *kMCLogIDEConsoleAreaKey; - -@implementation NSSearchField (MCLog) - -- (void)setConsoleArea:(MCLogIDEConsoleArea *)consoleArea +void hookIDEConsoleArea() { - objc_setAssociatedObject(self, &kMCLogIDEConsoleAreaKey, consoleArea, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + 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); + IMP newImpl = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_clearText)); + method_setImplementation(clearText, newImpl); } -- (MCLogIDEConsoleArea *)consoleArea +void hookIDEConsoleItem() { - return objc_getAssociatedObject(self, &kMCLogIDEConsoleAreaKey); + 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)setConsoleTextView:(NSTextView *)consoleTextView +void hookDVTTextStorage() { - objc_setAssociatedObject(self, &kMCLogConsoleTextViewKey, consoleTextView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + Class DVTTextStorage = NSClassFromString(@"DVTTextStorage"); + + Method fixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(fixAttributesInRange:)); + OriginalFixAttributesInRangeIMP = method_getImplementation(fixAttributesInRange); + IMP newFixAttributesInRangeIMP = class_getMethodImplementation([MCDVTTextStorage class], @selector(fixAttributesInRange:)); + method_setImplementation(fixAttributesInRange, newFixAttributesInRangeIMP); } -- (NSTextView *)consoleTextView +void hookIDEConsoleAdaptor() { - return objc_getAssociatedObject(self, &kMCLogConsoleTextViewKey); + 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); } -@dynamic consoleArea; -@dynamic consoleTextView; -@end - -#pragma mark - MCLogIDEConsoleArea +#pragma mark - util methods -@implementation MCLogIDEConsoleArea - -- (BOOL)_shouldAppendItem:(id)obj; +NSRegularExpression * logItemPrefixPattern() { - NSSearchField *searchField = getSearchField(self); - if (!searchField) { - return YES; - } - - if (!searchField.consoleArea) { - searchField.consoleArea = self; - } - - NSMutableDictionary *originConsoleItems = [originConsoleItemsMap objectForKey:hash(self)]; - if (!originConsoleItems) { - originConsoleItems = [NSMutableDictionary dictionary]; - } - - // store all console items. - [originConsoleItems setObject:obj forKey:@([obj timestamp])]; - [originConsoleItemsMap setObject:originConsoleItems forKey:hash(self)]; - - if (![searchField.stringValue length]) { - return YES; - } - - // test with the regular expression - NSString *content = [obj content]; - NSRange range = NSMakeRange(0, content.length); - NSError *error; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:searchField.stringValue - options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators) - error:&error]; - if (regex == nil) { - // display all if with regex is error - NSLog(@"%s, error:%@", __PRETTY_FUNCTION__, error); - return YES; + 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); + } } - - NSArray *matches = [regex matchesInString:content options:0 range:range]; - if ([matches count]) { - return YES; - } - - return NO; + return pattern; } -- (void)_clearText +NSRegularExpression * escCharPattern() { - _clearText(self, _cmd); - [originConsoleItemsMap removeObjectForKey:hash(self)]; -} -@end - -#pragma mark - - -void replaceShouldAppendItemMethod() -{ - Class IDEConsoleArea = NSClassFromString(@"IDEConsoleArea"); - Method originalMethod = class_getInstanceMethod([IDEConsoleArea class], @selector(_shouldAppendItem:)); - - IMP newImpl = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_shouldAppendItem:)); - method_setImplementation(originalMethod, newImpl); -} - -void replaceClearTextMethod() -{ - 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); + 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) @@ -332,4 +843,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