diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f08590f --- /dev/null +++ b/.clang-format @@ -0,0 +1,66 @@ +# +# clang-format的最基本code style是LLVM, +# 其他的所有内置code style都是继承于 LLVM, +# 下边是 clang-format 的全部59个配置项 +# +BasedOnStyle: Google +Language: Cpp +### +AccessModifierOffset: -2 +AlignAfterOpenBracket: true +AlignOperands: true +AlignTrailingComments: true +AlignConsecutiveAssignments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +#AlwaysBreakAfterDefinitionReturnType: None +BinPackParameters: true +BinPackArguments: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakBeforeBraces: Attach +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 120 +CommentPragmas: "^ IWYU pragma:" +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +ExperimentalAutoDetectBinPacking: false +IndentWrappedFunctionNames: false +IndentWidth: 4 +TabWidth: 8 +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 4 +PointerAlignment: Right +UseTab: Never +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceInEmptyParentheses: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpaceAfterCStyleCast: true +SpaceBeforeParens: ControlStatements +SpaceBeforeAssignmentOperators: true +SpacesInAngles: false +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +AlignEscapedNewlinesLeft: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +DerivePointerAlignment: true +IndentCaseLabels: true +KeepEmptyLinesAtTheStartOfBlocks: false +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +SpacesBeforeTrailingComments: 2 +Standard: Auto +PenaltyReturnTypeOnItsOwnLine: 200 +PenaltyBreakBeforeFirstCallParameter: 1 diff --git a/.gitignore b/.gitignore index 51107b6..e5f41e4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ xcuserdata *.moved-aside DerivedData *.xcuserstate - +bak/ diff --git a/MCLog.xcodeproj/project.pbxproj b/MCLog.xcodeproj/project.pbxproj index 29c8aea..79f009a 100644 --- a/MCLog.xcodeproj/project.pbxproj +++ b/MCLog.xcodeproj/project.pbxproj @@ -7,13 +7,77 @@ objects = { /* Begin PBXBuildFile section */ + 436253401C413D030091D39D /* ALAssociatedWeakObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 4362533F1C413D030091D39D /* ALAssociatedWeakObject.m */; }; + 436980241C3C34A200BB7702 /* HHTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 436980231C3C34A200BB7702 /* HHTimer.m */; }; + 438950261C3E6E0A00B34D24 /* PluginConfigs.m in Sources */ = {isa = PBXBuildFile; fileRef = 438950251C3E6E0A00B34D24 /* PluginConfigs.m */; }; + 43A0EFAD1C3C449000F54B51 /* NSSearchField+MCLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A0EFAC1C3C448F00F54B51 /* NSSearchField+MCLog.m */; }; + 43A0EFB01C3C459100F54B51 /* MCIDEConsoleItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A0EFAF1C3C459100F54B51 /* MCIDEConsoleItem.m */; }; + 43A0EFB31C3C46CF00F54B51 /* MCLogIDEConsoleArea.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A0EFB21C3C46CF00F54B51 /* MCLogIDEConsoleArea.m */; }; + 43A0EFB61C3CB0AB00F54B51 /* MCIDEConsoleAdaptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A0EFB51C3CB0AA00F54B51 /* MCIDEConsoleAdaptor.m */; }; + 43A0EFB91C3CB2C800F54B51 /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A0EFB81C3CB2C800F54B51 /* Utils.m */; }; + 43A0EFCC1C3CBC4100F54B51 /* MethodSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A0EFCB1C3CBC4100F54B51 /* MethodSwizzle.m */; }; + 43A0EFCF1C3CC82900F54B51 /* NSView+MCLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A0EFCE1C3CC82900F54B51 /* NSView+MCLog.m */; }; + 43BEF0481C3C3A31004A9D3C /* MCOrderedMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 43BEF0471C3C3A31004A9D3C /* MCOrderedMap.m */; }; + 43C26A021A9D774E00BE9280 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 43C26A011A9D774E00BE9280 /* main.m */; }; + 43C26A051A9D774E00BE9280 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 43C26A041A9D774E00BE9280 /* AppDelegate.m */; }; + 43C26A071A9D774E00BE9280 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43C26A061A9D774E00BE9280 /* Images.xcassets */; }; + 43C26A0A1A9D774E00BE9280 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 43C26A081A9D774E00BE9280 /* LaunchScreen.xib */; }; + 43C26A161A9D774E00BE9280 /* MCLogIOS_DemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 43C26A151A9D774E00BE9280 /* MCLogIOS_DemoTests.m */; }; + 43C26A1F1A9D77B500BE9280 /* TestViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 43C26A1E1A9D77B500BE9280 /* TestViewController.m */; }; + 43F368CA1C3FF7710083F0BC /* MCIDEConsoleTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 43F368C91C3FF7710083F0BC /* MCIDEConsoleTextView.m */; }; 8CBAF63C198B88C70067171A /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CBAF63B198B88C70067171A /* CoreFoundation.framework */; }; 8CBAF642198B88C70067171A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8CBAF640198B88C70067171A /* InfoPlist.strings */; }; 8CBAF64B198B88DD0067171A /* MCLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CBAF64A198B88DD0067171A /* MCLog.m */; }; 8CEB067E1BFADD0F00ED4438 /* MCDVTTextStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CEB067D1BFADD0F00ED4438 /* MCDVTTextStorage.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 43C26A101A9D774E00BE9280 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8CBAF630198B88C70067171A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43C269FC1A9D774E00BE9280; + remoteInfo = MCLogIOS_Demo; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 4362533E1C413D030091D39D /* ALAssociatedWeakObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALAssociatedWeakObject.h; sourceTree = ""; }; + 4362533F1C413D030091D39D /* ALAssociatedWeakObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALAssociatedWeakObject.m; sourceTree = ""; }; + 436980221C3C34A200BB7702 /* HHTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HHTimer.h; sourceTree = ""; }; + 436980231C3C34A200BB7702 /* HHTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HHTimer.m; sourceTree = ""; }; + 438950241C3E6E0A00B34D24 /* PluginConfigs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PluginConfigs.h; sourceTree = ""; }; + 438950251C3E6E0A00B34D24 /* PluginConfigs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PluginConfigs.m; sourceTree = ""; }; + 43A0EFAB1C3C448F00F54B51 /* NSSearchField+MCLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSSearchField+MCLog.h"; sourceTree = ""; }; + 43A0EFAC1C3C448F00F54B51 /* NSSearchField+MCLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSSearchField+MCLog.m"; sourceTree = ""; }; + 43A0EFAE1C3C459100F54B51 /* MCIDEConsoleItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCIDEConsoleItem.h; sourceTree = ""; }; + 43A0EFAF1C3C459100F54B51 /* MCIDEConsoleItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCIDEConsoleItem.m; sourceTree = ""; }; + 43A0EFB11C3C46CF00F54B51 /* MCLogIDEConsoleArea.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCLogIDEConsoleArea.h; sourceTree = ""; }; + 43A0EFB21C3C46CF00F54B51 /* MCLogIDEConsoleArea.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCLogIDEConsoleArea.m; sourceTree = ""; }; + 43A0EFB41C3CB0AA00F54B51 /* MCIDEConsoleAdaptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCIDEConsoleAdaptor.h; sourceTree = ""; }; + 43A0EFB51C3CB0AA00F54B51 /* MCIDEConsoleAdaptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCIDEConsoleAdaptor.m; sourceTree = ""; }; + 43A0EFB71C3CB2C800F54B51 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = ""; }; + 43A0EFB81C3CB2C800F54B51 /* Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utils.m; sourceTree = ""; }; + 43A0EFCA1C3CBC4100F54B51 /* MethodSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MethodSwizzle.h; sourceTree = ""; }; + 43A0EFCB1C3CBC4100F54B51 /* MethodSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MethodSwizzle.m; sourceTree = ""; }; + 43A0EFCD1C3CC82900F54B51 /* NSView+MCLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSView+MCLog.h"; sourceTree = ""; }; + 43A0EFCE1C3CC82900F54B51 /* NSView+MCLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSView+MCLog.m"; sourceTree = ""; }; + 43BEF0461C3C3A31004A9D3C /* MCOrderedMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCOrderedMap.h; sourceTree = ""; }; + 43BEF0471C3C3A31004A9D3C /* MCOrderedMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCOrderedMap.m; sourceTree = ""; }; + 43C269FD1A9D774E00BE9280 /* MCLogIOS_Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MCLogIOS_Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 43C26A001A9D774E00BE9280 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 43C26A011A9D774E00BE9280 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 43C26A031A9D774E00BE9280 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 43C26A041A9D774E00BE9280 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 43C26A061A9D774E00BE9280 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 43C26A091A9D774E00BE9280 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 43C26A0F1A9D774E00BE9280 /* MCLogIOS_DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MCLogIOS_DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 43C26A141A9D774E00BE9280 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 43C26A151A9D774E00BE9280 /* MCLogIOS_DemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MCLogIOS_DemoTests.m; sourceTree = ""; }; + 43C26A1D1A9D77B500BE9280 /* TestViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestViewController.h; sourceTree = ""; }; + 43C26A1E1A9D77B500BE9280 /* TestViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestViewController.m; sourceTree = ""; }; + 43F368C81C3FF7710083F0BC /* MCIDEConsoleTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCIDEConsoleTextView.h; sourceTree = ""; }; + 43F368C91C3FF7710083F0BC /* MCIDEConsoleTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCIDEConsoleTextView.m; sourceTree = ""; }; 8CBAF638198B88C70067171A /* MCLog.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MCLog.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 8CBAF63B198B88C70067171A /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 8CBAF63F198B88C70067171A /* MCLog-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MCLog-Info.plist"; sourceTree = ""; }; @@ -27,6 +91,20 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 43C269FA1A9D774E00BE9280 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43C26A0C1A9D774E00BE9280 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8CBAF635198B88C70067171A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -38,10 +116,90 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 43BEF0441C3C399E004A9D3C /* xcode-extensions */ = { + isa = PBXGroup; + children = ( + 8CEB067C1BFADD0F00ED4438 /* MCDVTTextStorage.h */, + 8CEB067D1BFADD0F00ED4438 /* MCDVTTextStorage.m */, + 43A0EFAB1C3C448F00F54B51 /* NSSearchField+MCLog.h */, + 43A0EFAC1C3C448F00F54B51 /* NSSearchField+MCLog.m */, + 43A0EFAE1C3C459100F54B51 /* MCIDEConsoleItem.h */, + 43A0EFAF1C3C459100F54B51 /* MCIDEConsoleItem.m */, + 43A0EFB11C3C46CF00F54B51 /* MCLogIDEConsoleArea.h */, + 43A0EFB21C3C46CF00F54B51 /* MCLogIDEConsoleArea.m */, + 43A0EFB41C3CB0AA00F54B51 /* MCIDEConsoleAdaptor.h */, + 43A0EFB51C3CB0AA00F54B51 /* MCIDEConsoleAdaptor.m */, + 43F368C81C3FF7710083F0BC /* MCIDEConsoleTextView.h */, + 43F368C91C3FF7710083F0BC /* MCIDEConsoleTextView.m */, + ); + name = "xcode-extensions"; + sourceTree = ""; + }; + 43BEF0451C3C39DD004A9D3C /* Utils */ = { + isa = PBXGroup; + children = ( + 436980221C3C34A200BB7702 /* HHTimer.h */, + 436980231C3C34A200BB7702 /* HHTimer.m */, + 43BEF0461C3C3A31004A9D3C /* MCOrderedMap.h */, + 43BEF0471C3C3A31004A9D3C /* MCOrderedMap.m */, + 43A0EFB71C3CB2C800F54B51 /* Utils.h */, + 43A0EFB81C3CB2C800F54B51 /* Utils.m */, + 43A0EFCA1C3CBC4100F54B51 /* MethodSwizzle.h */, + 43A0EFCB1C3CBC4100F54B51 /* MethodSwizzle.m */, + 43A0EFCD1C3CC82900F54B51 /* NSView+MCLog.h */, + 43A0EFCE1C3CC82900F54B51 /* NSView+MCLog.m */, + 4362533E1C413D030091D39D /* ALAssociatedWeakObject.h */, + 4362533F1C413D030091D39D /* ALAssociatedWeakObject.m */, + ); + name = Utils; + sourceTree = ""; + }; + 43C269FE1A9D774E00BE9280 /* MCLogIOS_Demo */ = { + isa = PBXGroup; + children = ( + 43C26A031A9D774E00BE9280 /* AppDelegate.h */, + 43C26A041A9D774E00BE9280 /* AppDelegate.m */, + 43C26A061A9D774E00BE9280 /* Images.xcassets */, + 43C26A081A9D774E00BE9280 /* LaunchScreen.xib */, + 43C269FF1A9D774E00BE9280 /* Supporting Files */, + 43C26A1D1A9D77B500BE9280 /* TestViewController.h */, + 43C26A1E1A9D77B500BE9280 /* TestViewController.m */, + ); + path = MCLogIOS_Demo; + sourceTree = ""; + }; + 43C269FF1A9D774E00BE9280 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 43C26A001A9D774E00BE9280 /* Info.plist */, + 43C26A011A9D774E00BE9280 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 43C26A121A9D774E00BE9280 /* MCLogIOS_DemoTests */ = { + isa = PBXGroup; + children = ( + 43C26A151A9D774E00BE9280 /* MCLogIOS_DemoTests.m */, + 43C26A131A9D774E00BE9280 /* Supporting Files */, + ); + path = MCLogIOS_DemoTests; + sourceTree = ""; + }; + 43C26A131A9D774E00BE9280 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 43C26A141A9D774E00BE9280 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 8CBAF62F198B88C70067171A = { isa = PBXGroup; children = ( 8CBAF63D198B88C70067171A /* MCLog */, + 43C269FE1A9D774E00BE9280 /* MCLogIOS_Demo */, + 43C26A121A9D774E00BE9280 /* MCLogIOS_DemoTests */, 8CBAF63A198B88C70067171A /* Frameworks */, 8CBAF639198B88C70067171A /* Products */, ); @@ -51,6 +209,8 @@ isa = PBXGroup; children = ( 8CBAF638198B88C70067171A /* MCLog.xcplugin */, + 43C269FD1A9D774E00BE9280 /* MCLogIOS_Demo.app */, + 43C26A0F1A9D774E00BE9280 /* MCLogIOS_DemoTests.xctest */, ); name = Products; sourceTree = ""; @@ -66,12 +226,14 @@ 8CBAF63D198B88C70067171A /* MCLog */ = { isa = PBXGroup; children = ( - 8CBAF63E198B88C70067171A /* Supporting Files */, + 43BEF0451C3C39DD004A9D3C /* Utils */, + 43BEF0441C3C399E004A9D3C /* xcode-extensions */, 8CBAF649198B88DD0067171A /* MCLog.h */, 8CBAF64A198B88DD0067171A /* MCLog.m */, - 8CEB067C1BFADD0F00ED4438 /* MCDVTTextStorage.h */, - 8CEB067D1BFADD0F00ED4438 /* MCDVTTextStorage.m */, 8CEB067B1BFADA1700ED4438 /* MCXcodeHeaders.h */, + 8CBAF63E198B88C70067171A /* Supporting Files */, + 438950241C3E6E0A00B34D24 /* PluginConfigs.h */, + 438950251C3E6E0A00B34D24 /* PluginConfigs.m */, ); path = MCLog; sourceTree = ""; @@ -89,6 +251,41 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 43C269FC1A9D774E00BE9280 /* MCLogIOS_Demo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43C26A1B1A9D774E00BE9280 /* Build configuration list for PBXNativeTarget "MCLogIOS_Demo" */; + buildPhases = ( + 43C269F91A9D774E00BE9280 /* Sources */, + 43C269FA1A9D774E00BE9280 /* Frameworks */, + 43C269FB1A9D774E00BE9280 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MCLogIOS_Demo; + productName = MCLogIOS_Demo; + productReference = 43C269FD1A9D774E00BE9280 /* MCLogIOS_Demo.app */; + productType = "com.apple.product-type.application"; + }; + 43C26A0E1A9D774E00BE9280 /* MCLogIOS_DemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43C26A1C1A9D774E00BE9280 /* Build configuration list for PBXNativeTarget "MCLogIOS_DemoTests" */; + buildPhases = ( + 43C26A0B1A9D774E00BE9280 /* Sources */, + 43C26A0C1A9D774E00BE9280 /* Frameworks */, + 43C26A0D1A9D774E00BE9280 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 43C26A111A9D774E00BE9280 /* PBXTargetDependency */, + ); + name = MCLogIOS_DemoTests; + productName = MCLogIOS_DemoTests; + productReference = 43C26A0F1A9D774E00BE9280 /* MCLogIOS_DemoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 8CBAF637198B88C70067171A /* MCLog */ = { isa = PBXNativeTarget; buildConfigurationList = 8CBAF646198B88C70067171A /* Build configuration list for PBXNativeTarget "MCLog" */; @@ -112,8 +309,18 @@ 8CBAF630198B88C70067171A /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Yuhua Chen"; + TargetAttributes = { + 43C269FC1A9D774E00BE9280 = { + CreatedOnToolsVersion = 6.1.1; + DevelopmentTeam = D7348372R8; + }; + 43C26A0E1A9D774E00BE9280 = { + CreatedOnToolsVersion = 6.1.1; + TestTargetID = 43C269FC1A9D774E00BE9280; + }; + }; }; buildConfigurationList = 8CBAF633198B88C70067171A /* Build configuration list for PBXProject "MCLog" */; compatibilityVersion = "Xcode 3.2"; @@ -121,6 +328,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 8CBAF62F198B88C70067171A; productRefGroup = 8CBAF639198B88C70067171A /* Products */; @@ -128,11 +336,29 @@ projectRoot = ""; targets = ( 8CBAF637198B88C70067171A /* MCLog */, + 43C269FC1A9D774E00BE9280 /* MCLogIOS_Demo */, + 43C26A0E1A9D774E00BE9280 /* MCLogIOS_DemoTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 43C269FB1A9D774E00BE9280 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C26A0A1A9D774E00BE9280 /* LaunchScreen.xib in Resources */, + 43C26A071A9D774E00BE9280 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43C26A0D1A9D774E00BE9280 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8CBAF636198B88C70067171A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -144,18 +370,64 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 43C269F91A9D774E00BE9280 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C26A1F1A9D77B500BE9280 /* TestViewController.m in Sources */, + 43C26A051A9D774E00BE9280 /* AppDelegate.m in Sources */, + 43C26A021A9D774E00BE9280 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43C26A0B1A9D774E00BE9280 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C26A161A9D774E00BE9280 /* MCLogIOS_DemoTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8CBAF634198B88C70067171A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 43A0EFB91C3CB2C800F54B51 /* Utils.m in Sources */, + 43A0EFB61C3CB0AB00F54B51 /* MCIDEConsoleAdaptor.m in Sources */, + 438950261C3E6E0A00B34D24 /* PluginConfigs.m in Sources */, + 436980241C3C34A200BB7702 /* HHTimer.m in Sources */, + 43BEF0481C3C3A31004A9D3C /* MCOrderedMap.m in Sources */, + 43A0EFAD1C3C449000F54B51 /* NSSearchField+MCLog.m in Sources */, + 43A0EFCF1C3CC82900F54B51 /* NSView+MCLog.m in Sources */, + 43A0EFCC1C3CBC4100F54B51 /* MethodSwizzle.m in Sources */, 8CBAF64B198B88DD0067171A /* MCLog.m in Sources */, + 43A0EFB31C3C46CF00F54B51 /* MCLogIDEConsoleArea.m in Sources */, 8CEB067E1BFADD0F00ED4438 /* MCDVTTextStorage.m in Sources */, + 436253401C413D030091D39D /* ALAssociatedWeakObject.m in Sources */, + 43F368CA1C3FF7710083F0BC /* MCIDEConsoleTextView.m in Sources */, + 43A0EFB01C3C459100F54B51 /* MCIDEConsoleItem.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 43C26A111A9D774E00BE9280 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43C269FC1A9D774E00BE9280 /* MCLogIOS_Demo */; + targetProxy = 43C26A101A9D774E00BE9280 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ + 43C26A081A9D774E00BE9280 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 43C26A091A9D774E00BE9280 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; 8CBAF640198B88C70067171A /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -167,6 +439,96 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 43C26A171A9D774E00BE9280 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = MCLogIOS_Demo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "me.alexlee002.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 43C26A181A9D774E00BE9280 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_UNREACHABLE_CODE = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + INFOPLIST_FILE = MCLogIOS_Demo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "me.alexlee002.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 43C26A191A9D774E00BE9280 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = MCLogIOS_DemoTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "me.alexlee002.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCLogIOS_Demo.app/MCLogIOS_Demo"; + }; + name = Debug; + }; + 43C26A1A1A9D774E00BE9280 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = MCLogIOS_DemoTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "me.alexlee002.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCLogIOS_Demo.app/MCLogIOS_Demo"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; 8CBAF644198B88C70067171A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -184,6 +546,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -199,7 +562,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; @@ -232,7 +595,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.10; SDKROOT = macosx; }; name = Release; @@ -247,6 +610,7 @@ GCC_PREFIX_HEADER = "MCLog/MCLog-Prefix.pch"; INFOPLIST_FILE = "MCLog/MCLog-Info.plist"; INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; + PRODUCT_BUNDLE_IDENTIFIER = "io.michaelchen.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xcplugin; }; @@ -262,6 +626,7 @@ GCC_PREFIX_HEADER = "MCLog/MCLog-Prefix.pch"; INFOPLIST_FILE = "MCLog/MCLog-Info.plist"; INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; + PRODUCT_BUNDLE_IDENTIFIER = "io.michaelchen.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xcplugin; }; @@ -270,6 +635,24 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 43C26A1B1A9D774E00BE9280 /* Build configuration list for PBXNativeTarget "MCLogIOS_Demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43C26A171A9D774E00BE9280 /* Debug */, + 43C26A181A9D774E00BE9280 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 43C26A1C1A9D774E00BE9280 /* Build configuration list for PBXNativeTarget "MCLogIOS_DemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43C26A191A9D774E00BE9280 /* Debug */, + 43C26A1A1A9D774E00BE9280 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8CBAF633198B88C70067171A /* Build configuration list for PBXProject "MCLog" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/MCLog/ALAssociatedWeakObject.h b/MCLog/ALAssociatedWeakObject.h new file mode 100644 index 0000000..3acd503 --- /dev/null +++ b/MCLog/ALAssociatedWeakObject.h @@ -0,0 +1,19 @@ +// +// ALAssociatedWeakObject.h +// MCLog +// +// Created by Alex Lee on 1/9/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + + +//@see: http://stackoverflow.com/questions/22809848/objective-c-runtime-run-code-at-deallocation-of-any-object/31560217#31560217 + + +@interface NSObject (AssociatedWeakObject) + +- (void)mc_runAtDealloc:(void(^)(void))block; + +@end \ No newline at end of file diff --git a/MCLog/ALAssociatedWeakObject.m b/MCLog/ALAssociatedWeakObject.m new file mode 100644 index 0000000..0ce3ca8 --- /dev/null +++ b/MCLog/ALAssociatedWeakObject.m @@ -0,0 +1,57 @@ +// +// ALAssociatedWeakObject.m +// MCLog +// +// Created by Alex Lee on 1/9/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "ALAssociatedWeakObject.h" +#import + +typedef void (^deallocBlock)(void); + +@interface ALAssociatedWeakObject : NSObject + ++ (instancetype)weakAssociatedObjectWithDeallocCallback:(deallocBlock)block; + +@end + +@implementation ALAssociatedWeakObject { + deallocBlock _block; +} + ++ (instancetype)weakAssociatedObjectWithDeallocCallback:(deallocBlock)block { + return [[self alloc] initWithDeallocCallback:block]; +} + +- (instancetype)initWithDeallocCallback:(deallocBlock)block { + self = [super init]; + if (self) { + _block = [block copy]; + } + return self; +} + +- (void)dealloc { + if (_block) { + _block(); + } +} + +@end + + +@implementation NSObject (AssociatedWeakObject) +static const char kRunAtDeallocBlockKey; +- (void)mc_runAtDealloc:(void(^)(void))block { + if (block) { + ALAssociatedWeakObject *proxy = [ALAssociatedWeakObject weakAssociatedObjectWithDeallocCallback:block]; + objc_setAssociatedObject(self, + &kRunAtDeallocBlockKey, + proxy, + OBJC_ASSOCIATION_RETAIN); + } +} + +@end diff --git a/MCLog/HHTimer.h b/MCLog/HHTimer.h new file mode 100644 index 0000000..eda9c0b --- /dev/null +++ b/MCLog/HHTimer.h @@ -0,0 +1,24 @@ +// +// HHTimer.h +// BusinessLayer +// +// Created by lingaohe on 3/5/14. +// Copyright (c) 2014 Baidu. All rights reserved. +// + +#import + +@interface HHTimer : NSObject + ++ (HHTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds + dispatchQueue:(dispatch_queue_t)queue + block:(dispatch_block_t)block + userInfo:(id)userInfo + repeats:(BOOL)yesOrNo; + +- (void)fire; +- (void)invalidate; + +- (BOOL)isValid; +- (id)userInfo; +@end diff --git a/MCLog/HHTimer.m b/MCLog/HHTimer.m new file mode 100644 index 0000000..7d23020 --- /dev/null +++ b/MCLog/HHTimer.m @@ -0,0 +1,75 @@ +// +// HHTimer.m +// BusinessLayer +// +// Created by lingaohe on 3/5/14. +// Copyright (c) 2014 Baidu. All rights reserved. +// + +#import "HHTimer.h" + +@interface HHTimer () +@property(nonatomic, readwrite, copy) dispatch_block_t block; +@property(nonatomic, readwrite, strong) dispatch_source_t source; +@property(nonatomic, strong) id internalUserInfo; +@end + +@implementation HHTimer + +#pragma mark-- Init ++ (HHTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds + dispatchQueue:(dispatch_queue_t)queue + block:(dispatch_block_t)block + userInfo:(id)userInfo + repeats:(BOOL)yesOrNo { + NSParameterAssert(seconds); + NSParameterAssert(block); + + HHTimer *timer = [[self alloc] init]; + timer.internalUserInfo = userInfo; + timer.block = block; + timer.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + uint64_t nsec = (uint64_t)(seconds * NSEC_PER_SEC); + dispatch_source_set_timer(timer.source, dispatch_time(DISPATCH_TIME_NOW, nsec), nsec, 0); + void (^internalBlock)(void) = ^{ + if (!yesOrNo) { + block(); + [timer invalidate]; + } else { + block(); + } + }; + dispatch_source_set_event_handler(timer.source, internalBlock); + dispatch_resume(timer.source); + return timer; +} + + +- (void)dealloc { + [self invalidate]; +} +#pragma mark--Action +- (void)fire { + self.block(); +} + +- (void)invalidate { + if (self.source) { + dispatch_source_cancel(self.source); +#if !__has_feature(objc_arc) + dispatch_release(self.source); +#endif + self.source = nil; + } + self.block = nil; +} + +#pragma mark-- State +- (BOOL)isValid { + return (self.source != nil); +} + +- (id)userInfo { + return self.internalUserInfo; +} +@end diff --git a/MCLog/MCDVTTextStorage.h b/MCLog/MCDVTTextStorage.h index bf1c816..b58902e 100644 --- a/MCLog/MCDVTTextStorage.h +++ b/MCLog/MCDVTTextStorage.h @@ -8,14 +8,11 @@ #import "MCXcodeHeaders.h" -@interface NSTextStorage (MCDVTTextStorage) +@interface NSTextStorage(MCDVTTextStorage) -- (void)mc_fixAttributesInRange:(NSRange)range; -- (void)updateAttributes:(NSMutableDictionary *)attrs withANSIESCString:(NSString *)ansiEscString; +@property(nonatomic, strong) NSDictionary *currentAttributes; +@property(nonatomic, getter=isConsoleStorage) BOOL consoleStorage; -- (void)setLastAttribute:(NSDictionary *)attribute; -- (NSDictionary *)lastAttribute; -- (void)setConsoleStorage:(BOOL)consoleStorage; -- (BOOL)consoleStorage; +- (void)mc_fixAttributesInRange:(NSRange)range; @end \ No newline at end of file diff --git a/MCLog/MCDVTTextStorage.m b/MCLog/MCDVTTextStorage.m index 0f3de4a..70b980b 100644 --- a/MCLog/MCDVTTextStorage.m +++ b/MCLog/MCDVTTextStorage.m @@ -7,181 +7,343 @@ // #import "MCDVTTextStorage.h" +#import "MethodSwizzle.h" +#import "PluginConfigs.h" +#import "NSView+MCLog.h" +#import "Utils.h" #import -#define NSColorWithHexRGB(rgb) [NSColor colorWithCalibratedRed:((rgb) >> 16 & 0xFF) / 255.f green:((rgb) >> 8 & 0xFF) / 255.f blue:((rgb) & 0xFF) / 255.f alpha:1.f] -NSRegularExpression * escCharPattern(); +static inline NSColor *consoleTextViewBackgroundColor() { + static NSColor *color = nil; //when NSApp.isActive == NO; backgroundColor would return nil + + NSView *contentView = [[NSApp mainWindow] contentView]; + NSTextView *consoleTextView = [contentView descendantViewByClassName:@"IDEConsoleTextView"]; + if (!consoleTextView) { + return color; + } + if ([consoleTextView respondsToSelector:NSSelectorFromString(@"backgroundColor")]) { + color = [consoleTextView valueForKey:@"backgroundColor"] ?: color; + } + return color; +} + +static inline NSFont *convertFontStyle(NSFont *font, NSFontTraitMask mask) { + if (font == nil) { + return nil; + } + return [[NSFontManager sharedFontManager] fontWithFamily:font.familyName + traits:mask + weight:[[NSFontManager sharedFontManager] weightOfFont:font] + size:font.pointSize]; +} + + +void swizzleDVTTextStorage() { + Class DVTTextStorage = NSClassFromString(@"DVTTextStorage"); + Method fixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(fixAttributesInRange:)); + Method swizzledFixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(mc_fixAttributesInRange:)); + + BOOL didAddMethod = class_addMethod(DVTTextStorage, @selector(fixAttributesInRange:), + method_getImplementation(swizzledFixAttributesInRange), + method_getTypeEncoding(swizzledFixAttributesInRange)); + if (didAddMethod) { + class_replaceMethod(DVTTextStorage, @selector(mc_fixAttributesInRange:), + method_getImplementation(fixAttributesInRange), + method_getTypeEncoding(swizzledFixAttributesInRange)); + } else { + method_exchangeImplementations(fixAttributesInRange, swizzledFixAttributesInRange); + } +} @implementation NSTextStorage (MCDVTTextStorage) -- (void)mc_fixAttributesInRange:(NSRange)range -{ - [self mc_fixAttributesInRange:range]; - - if (!self.consoleStorage) { - return; - } - - __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; ++ (void)load { + swizzleDVTTextStorage(); } -- (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; - } - } +- (void)mc_fixAttributesInRange:(NSRange)range { + [self mc_fixAttributesInRange:range]; + + if (!self.consoleStorage) { + return; + } + + __block NSRange lastRange = NSMakeRange(range.location, 0); + NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; + if (self.currentAttributes.count > 0) { + [attrs setValuesForKeysWithDictionary:self.currentAttributes]; + self.currentAttributes = nil; + } + + static NSRegularExpression *regex = nil; + if (regex == nil) { + regex = escCharPattern(); + } + [regex enumerateMatchesInString:self.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + + NSMutableDictionary *formalAttrs = + [NSMutableDictionary dictionaryWithCapacity:attrs.count]; + [attrs enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, + BOOL *_Nonnull stop) { + if (![key hasPrefix:@"mc_"]) { + formalAttrs[key] = obj; + } + }]; + + if (formalAttrs.count > 0) { + NSRange attrRange = + NSMakeRange(lastRange.location + lastRange.length, + result.range.location - lastRange.location - lastRange.length); + [self addAttributes:formalAttrs range:attrRange]; + } + + NSString *attrsDesc = [self.string substringWithRange:[result rangeAtIndex:1]]; + + if (attrsDesc.length > 0) { + [self addAttributesWithANSIEscValue:attrsDesc toAttributes:attrs]; + } + + [self addAttributes:@{ + NSFontAttributeName : [NSFont systemFontOfSize:0.000001f], + NSForegroundColorAttributeName : [NSColor clearColor] + } + range:result.range]; + + lastRange = result.range; + + }]; + self.currentAttributes = attrs; } -- (void)setLastAttribute:(NSDictionary *)attribute -{ - objc_setAssociatedObject(self, @selector(lastAttribute), attribute, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (NSFont *)defaultFont { + static NSFont *font = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *xcAttrs = [self attributesAtIndex:0 effectiveRange:nil]; + font = xcAttrs[NSFontAttributeName] ?: [NSFont systemFontOfSize:11.f]; + }); + return font; } -- (NSDictionary *)lastAttribute -{ - return objc_getAssociatedObject(self, @selector(lastAttribute)); +- (NSColor *)defaultTextColor { + static NSColor *color = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *xcAttrs = [self attributesAtIndex:0 effectiveRange:nil]; + color = xcAttrs[NSForegroundColorAttributeName] ?: [NSColor blackColor]; + }); + return color; } -- (void)setConsoleStorage:(BOOL)consoleStorage -{ - objc_setAssociatedObject(self, @selector(consoleStorage), @(consoleStorage), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (NSColor *)defaultBackgroundColor { + static NSColor *color = nil; + if (color == nil) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + color = consoleTextViewBackgroundColor(); + if (color == nil) { + NSDictionary *xcAttrs = [self attributesAtIndex:0 effectiveRange:nil]; + color = xcAttrs[NSBackgroundColorAttributeName] ?: [NSColor whiteColor]; + } + }); + } + return color; } -- (BOOL)consoleStorage -{ - return [objc_getAssociatedObject(self, @selector(consoleStorage)) boolValue]; +// not fully test. +// the following implement is base on my test in terminal.app of OS X 10.11 +- (void)addAttributesWithANSIEscValue:(NSString *)ansiEscString toAttributes:(NSMutableDictionary *)attrs { + + __block NSInteger fgColorCode = 0; + __block NSInteger bgColorCode = 0; + + [[ansiEscString componentsSeparatedByString:@";"] + enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + NSInteger attCode = [obj integerValue]; + + // 1/21; 3/23; 4/24 => Font style + // 2/22; 7/27; 8/28; 39/49 => Color style + switch (attCode) { + case 0: + [attrs removeAllObjects]; + break; + + case 1: // bold + attrs[@"mc_font_bolded"] = @(YES); + break; + + case 21: // unBold + [attrs removeObjectForKey:@"mc_font_bolded"]; + break; + + case 3: // italic + attrs[@"mc_font_is_italic"] = @(YES); + break; + + case 23: // italic off + [attrs removeObjectForKey:@"mc_font_is_italic"]; + break; + + case 4: // underline + case 24: // underline off + attrs[NSUnderlineStyleAttributeName] = attCode == 4 ? @( NSUnderlineStyleSingle ) : @( NSUnderlineStyleNone ); + break; + + case 2: // Faint (decreased intensity) + attrs[@"mc_color_fainted"] = @(YES); + break; + + case 22: // normal intensity + [attrs removeObjectForKey:@"mc_color_fainted"]; + break; + + case 7: // image negative + attrs[@"mc_image_negatived"] = @(YES); + break; + + case 27: // image positive + [attrs removeObjectForKey:@"mc_image_negatived"]; + break; + + case 8: // Conceal + attrs[@"mc_text_concealed"] = @(YES); + break; + + case 28: // Conceal off + [attrs removeObjectForKey:@"mc_text_concealed"]; + break; + + case 39: // reset text color + attrs[NSForegroundColorAttributeName] = [self defaultTextColor]; + break; + + case 49: // reset background color + attrs[NSBackgroundColorAttributeName] = [self defaultBackgroundColor]; + break; + + //foreground color + case 30: //black + case 31: // Red + case 32: // Green + case 33: // Yellow + case 34: // Blue + case 35: // purple + case 36: // cyan + case 37: // gray + fgColorCode = attCode; + break; + + // background color + case 40: //black + case 41: // Red + case 42: // Green + case 43: // Yellow + case 44: // Blue + case 45: // purple + case 46: // cyan + case 47: // gray + bgColorCode = attCode; + break; + + default: + break; + } + }]; + + + NSFont *font = [self defaultFont]; + NSFontTraitMask fontMask = 0; + if ([attrs[@"mc_font_bolded"] boolValue]) { + fontMask |= NSBoldFontMask; + } + if ([attrs[@"mc_font_is_italic"] boolValue]) { + fontMask |= NSItalicFontMask; + } + attrs[NSFontAttributeName] = convertFontStyle(font, fontMask); + + BOOL isFaint = [attrs[@"mc_color_fainted"] boolValue]; + + NSColor *fgColor = nil; + NSColor *bgColor = nil; + + if (fgColorCode == 0) { + fgColorCode = [attrs[@"mc_fgcolor_num"] integerValue]; + } + if (bgColorCode == 0) { + bgColorCode = [attrs[@"mc_bgcolor_num"] integerValue]; + } + + if (fgColorCode != 0) { + attrs[@"mc_fgcolor_num"] = @(fgColorCode); + fgColor = ANSICodeToNSColor(fgColorCode, useBrightColorStyle() && !isFaint); + } else { + fgColor = attrs[NSForegroundColorAttributeName]; + } + + if (bgColorCode != 0) { + attrs[@"mc_bgcolor_num"] = @(bgColorCode); + bgColor = ANSICodeToNSColor(bgColorCode, useBrightColorStyle() && !isFaint); + } else { + bgColor = attrs[NSBackgroundColorAttributeName]; + } + + if ([attrs[@"mc_image_negatived"] boolValue] && ![attrs[@"mc_image_negatived_set"] boolValue]) { + NSColor *swap = fgColor; + fgColor = bgColor; + bgColor = swap; + + attrs[@"mc_image_negatived_set"] = @(YES); + } else if ([attrs[@"mc_image_negatived_set"] boolValue]) { + NSColor *swap = fgColor; + fgColor = bgColor; + bgColor = swap; + + [attrs removeObjectForKey:@"mc_image_negatived_set"]; + } + + if ([attrs[@"mc_text_concealed"] boolValue]) { + attrs[@"mc_fgcolor_conceal"] = fgColor; + fgColor = [NSColor clearColor]; + } else { + fgColor = attrs[@"mc_fgcolor_conceal"] ?: fgColor; + [attrs removeObjectForKey:@"mc_fgcolor_conceal"]; + } + + if (fgColor) { + attrs[NSForegroundColorAttributeName] = fgColor; + } else { + [attrs removeObjectForKey:NSForegroundColorAttributeName]; + } + if (bgColor) { + attrs[NSBackgroundColorAttributeName] = bgColor; + } else { + [attrs removeObjectForKey:NSBackgroundColorAttributeName]; + } } -@end +- (void)setCurrentAttributes:(NSDictionary *)attributes { + objc_setAssociatedObject(self, @selector(currentAttributes), attributes, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSDictionary *)currentAttributes { + return objc_getAssociatedObject(self, @selector(currentAttributes)); +} -#pragma mark - Utilities - -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; +- (void)setConsoleStorage:(BOOL)consoleStorage { + objc_setAssociatedObject(self, @selector(isConsoleStorage), @(consoleStorage), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + +- (BOOL)isConsoleStorage { + return [objc_getAssociatedObject(self, @selector(isConsoleStorage)) boolValue]; +} + +@end + + + + + diff --git a/MCLog/MCIDEConsoleAdaptor.h b/MCLog/MCIDEConsoleAdaptor.h new file mode 100644 index 0000000..c940f44 --- /dev/null +++ b/MCLog/MCIDEConsoleAdaptor.h @@ -0,0 +1,17 @@ +// +// MCIDEConsoleAdaptor.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +@class HHTimer; +@interface MCIDEConsoleAdaptor: NSObject + +- (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedByUser:(BOOL)arg3; + +@end + diff --git a/MCLog/MCIDEConsoleAdaptor.m b/MCLog/MCIDEConsoleAdaptor.m new file mode 100644 index 0000000..a66e61d --- /dev/null +++ b/MCLog/MCIDEConsoleAdaptor.m @@ -0,0 +1,158 @@ +// +// MCIDEConsoleAdaptor.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import +#import "HHTimer.h" +#import "MCIDEConsoleAdaptor.h" +#import "MethodSwizzle.h" +#import "Utils.h" + +static const void *kUnProcessedOutputKey; +static const void *kUnProcessedOutputTimerKey; + +static dispatch_queue_t buffer_queue() { + static dispatch_queue_t mclog_buffer_queue = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + mclog_buffer_queue = dispatch_queue_create("io.michaelchen.mclog.buffer-queue", DISPATCH_QUEUE_SERIAL); + }); + + return mclog_buffer_queue; +} + + +@interface NSObject (MCIDEConsoleAdaptor) + +@property(nonatomic, strong) HHTimer *timer; +@property(nonatomic, strong) NSDictionary *unprocessedOutputInfo; + +- (void)mc_outputUnprocessedBuffer; + +@end + +@implementation NSObject (MCIDEConsoleAdaptor) + +- (void)setTimer:(HHTimer *)timer { + objc_setAssociatedObject(self, &kUnProcessedOutputTimerKey, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (HHTimer *)timer { + return objc_getAssociatedObject(self, &kUnProcessedOutputTimerKey); +} + +- (void)setUnprocessedOutputInfo:(NSDictionary *)outputInfo { + objc_setAssociatedObject(self, &kUnProcessedOutputKey, outputInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSDictionary *)unprocessedOutputInfo { + return objc_getAssociatedObject(self, &kUnProcessedOutputKey); +} + +- (IMP)originalOutputIMP { + static IMP originalIMP = nil; + if (originalIMP == nil) { + Class clazz = NSClassFromString(@"IDEConsoleAdaptor"); + SEL selector = @selector(outputForStandardOutput:isPrompt:isOutputRequestedByUser:); + originalIMP = [MethodSwizzleHelper originalIMPForClass:clazz selector:selector]; + } + return originalIMP; +} + +- (void)mc_outputUnprocessedBuffer { + NSDictionary *unprocessedOutputInfo = self.unprocessedOutputInfo; + if (unprocessedOutputInfo) { + [self setUnprocessedOutputInfo:nil]; + + [self originalOutputIMP](self, _cmd, [unprocessedOutputInfo[@"content"] stringValue], + [unprocessedOutputInfo[@"isPrompt"] boolValue], + [unprocessedOutputInfo[@"isOutputRequestedByUser"] boolValue]); + } +} + +- (void)invokeOriginalOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedByUser:(BOOL)arg3 { + dispatch_async(buffer_queue(), ^{ + [self originalOutputIMP](self, _cmd, arg1, arg2, arg3); + }); +} + +@end + +@implementation MCIDEConsoleAdaptor + ++ (void)load { + Class clazz = NSClassFromString(@"IDEConsoleAdaptor"); + SEL selector = @selector(outputForStandardOutput:isPrompt:isOutputRequestedByUser:); + IMP hookIMP = class_getMethodImplementation([MCIDEConsoleAdaptor class], selector); + [MethodSwizzleHelper swizzleMethodForClass:clazz selector:selector replacementIMP:hookIMP isClassMethod:NO]; +} + +- (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedByUser:(BOOL)arg3 { + if (![arg1 isKindOfClass:[NSString class]]) { // nil is not allowed either + return; + } + + [self.timer invalidate]; + self.timer = nil; + + NSRegularExpression *logSeperatorPattern = logItemPrefixPattern(); + + NSString *unprocessedString = self.unprocessedOutputInfo[@"content"]; + dispatch_async(buffer_queue(), ^{ + [self setUnprocessedOutputInfo:nil]; + }); + + + NSString *buffer = arg1; + if (unprocessedString.length > 0) { + buffer = [unprocessedString stringByAppendingString:arg1]; + } + + 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)]; + + [self invokeOriginalOutput:logItemData isPrompt:arg2 isOutputRequestedByUser:arg3]; + } + lastMatchingRange = result.range; + } + + if (lastMatchingRange.location + lastMatchingRange.length < [buffer length]) { + unprocessedString = [buffer substringFromIndex:lastMatchingRange.location]; + } + + } else { + [self invokeOriginalOutput:buffer isPrompt:arg2 isOutputRequestedByUser:arg3]; + } + } else { + [self invokeOriginalOutput:arg1 isPrompt:arg2 isOutputRequestedByUser:arg3]; + } + + if (unprocessedString.length > 0) { + [self setUnprocessedOutputInfo:@{ + @"content" : unprocessedString, + @"isPrompt" : @(arg2), + @"isOutputRequestedByUser" : @(arg3) + }]; + + self.timer = [HHTimer scheduledTimerWithTimeInterval:0.05f + dispatchQueue:buffer_queue() + block:^{ + [self mc_outputUnprocessedBuffer]; + } + userInfo:nil + repeats:NO]; + } +} + +@end \ No newline at end of file diff --git a/MCLog/MCIDEConsoleItem.h b/MCLog/MCIDEConsoleItem.h new file mode 100644 index 0000000..b8843c1 --- /dev/null +++ b/MCLog/MCIDEConsoleItem.h @@ -0,0 +1,32 @@ +// +// MCIDEConsoleItem.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +typedef NS_ENUM(NSUInteger, MCLogLevel) { + MCLogLevelVerbose = 0x1000, + MCLogLevelInfo, + MCLogLevelWarn, + MCLogLevelError +}; + + +@interface NSObject (MCIDEConsoleItem) + +@property(nonatomic) NSUInteger logLevel; +//@property(nonatomic) NSAttributedString *attributeString; + +@end + + + +@interface MCIDEConsoleItem : NSObject + +- (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3; + +@end diff --git a/MCLog/MCIDEConsoleItem.m b/MCLog/MCIDEConsoleItem.m new file mode 100644 index 0000000..7af489f --- /dev/null +++ b/MCLog/MCIDEConsoleItem.m @@ -0,0 +1,139 @@ +// +// MCIDEConsoleItem.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "MCIDEConsoleItem.h" +#import "Utils.h" +#import "MethodSwizzle.h" +#import "PluginConfigs.h" +#import + + + +static inline void updateItemAttribute(id item); + +static const void *kLogLevelAssociateKey; +@implementation NSObject (MCIDEConsoleItem) + +- (void)setLogLevel:(NSUInteger)loglevel { + objc_setAssociatedObject(self, &kLogLevelAssociateKey, @(loglevel), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSUInteger)logLevel { + return [objc_getAssociatedObject(self, &kLogLevelAssociateKey) unsignedIntegerValue]; +} + +@end + + + +@implementation MCIDEConsoleItem + ++ (void)load { + Class clazz = NSClassFromString(@"IDEConsoleItem"); + SEL selector = @selector(initWithAdaptorType:content:kind:); + IMP hookIMP = class_getMethodImplementation([MCIDEConsoleItem class], selector); + + [MethodSwizzleHelper swizzleMethodForClass:clazz + selector:selector + replacementIMP:hookIMP + isClassMethod:NO]; +} + +- (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3 +{ + static IMP IDEConsoleItemInitIMP = nil; + if (IDEConsoleItemInitIMP == nil) { + Class clazz = NSClassFromString(@"IDEConsoleItem"); + SEL selector = @selector(initWithAdaptorType:content:kind:); + IDEConsoleItemInitIMP = [MethodSwizzleHelper originalIMPForClass:clazz selector:selector]; + } + + id item = IDEConsoleItemInitIMP(self, _cmd, arg1, arg2, arg3); + updateItemAttribute(item); + return item; +} + +@end + + +static inline void updateItemAttribute(id item) { + NSString *logText = [item valueForKey:@"content"]; + if (!logText) { + return; + } + + if ([[item valueForKey:@"error"] boolValue]) { + logText = [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), logText]; + [item setValue:logText forKey:@"content"]; + return; + } + + if (![[item valueForKey:@"output"] boolValue] || [[item valueForKey:@"outputRequestedByUser"] boolValue]) { + return; + } + + static NSRegularExpression *prefixRegex = nil; + if (prefixRegex == nil) { + prefixRegex = logItemPrefixPattern(); + } + NSRange prefixRange = + [prefixRegex rangeOfFirstMatchInString:logText options:0 range:NSMakeRange(0, logText.length)]; + if (prefixRange.location != 0 || logText.length <= prefixRange.length) { + return; + } + + static NSRegularExpression *escRegex = nil; + if (escRegex == nil) { + escRegex = escCharPattern(); + } + + NSString *content = [logText substringFromIndex:prefixRange.length]; + NSString *originalContent = [escRegex 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]; + } else { + static NSRegularExpression *kCommonErrorLogPatterh; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *pattern = + @"\\s*\\*\\*\\* Terminating app due to uncaught exception '.+', reason: '[\\s\\S]+'\\n*\\*\\*\\* First throw call stack:\\s*\\n|" + @"\\s*(\\+|-)\\[[a-zA-Z_]\\w*\\s[a-zA-Z_]\\w*[(:([a-zA-Z_]\\w*)?)]*\\]: unrecognized selector sent to (class|instance) [\\dxXa-fA-F]+|" + @"\\s*\\*\\*\\* Assertion failure in (\\+|-)\\[[a-zA-Z_]\\w*\\s[a-zA-Z_]\\w*[(:([a-zA-Z_]\\w*)?)]*\\],|" + @"\\s*\\*\\*\\* Terminating app due to uncaught exception of class '[a-zA-Z_]\\w+'"; + NSError *error = nil; + kCommonErrorLogPatterh = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; + if (kCommonErrorLogPatterh == nil) { + MCLogger(@"ERROR:%@", error); + } + }); + if ([kCommonErrorLogPatterh matchesInString:originalContent options:0 range:NSMakeRange(0, originalContent.length)].count > 0) { + content = [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), content]; + } + } + content = [content stringByReplacingOccurrencesOfString:(@"\n" LC_RESET) withString:(LC_RESET @"\n")]; + [item setValue:[[logText substringWithRange:prefixRange] stringByAppendingString:content] forKey:@"content"]; +} + + diff --git a/MCLog/MCIDEConsoleTextView.h b/MCLog/MCIDEConsoleTextView.h new file mode 100644 index 0000000..c8678ac --- /dev/null +++ b/MCLog/MCIDEConsoleTextView.h @@ -0,0 +1,15 @@ +// +// MCIDEConsoleTextView.h +// MCLog +// +// Created by Alex Lee on 1/8/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +@interface NSTextView (MCIDEConsoleTextView) + +- (BOOL)mc_writeSelectionToPasteboard:(NSPasteboard *)pboard type:(NSString *)type; + +@end diff --git a/MCLog/MCIDEConsoleTextView.m b/MCLog/MCIDEConsoleTextView.m new file mode 100644 index 0000000..97310ef --- /dev/null +++ b/MCLog/MCIDEConsoleTextView.m @@ -0,0 +1,51 @@ +// +// MCIDEConsoleTextView.m +// MCLog +// +// Created by Alex Lee on 1/8/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "MCIDEConsoleTextView.h" +#import +#import "Utils.h" + +void swizzleIDEConsoleTextView() { + Class DVTTextStorage = NSClassFromString(@"IDEConsoleTextView"); + Method originalMethod = class_getInstanceMethod(DVTTextStorage, @selector(writeSelectionToPasteboard:type:)); + Method swizzledMethod = class_getInstanceMethod(DVTTextStorage, @selector(mc_writeSelectionToPasteboard:type:)); + + BOOL didAddMethod = class_addMethod(DVTTextStorage, @selector(writeSelectionToPasteboard:type:), + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + if (didAddMethod) { + class_replaceMethod(DVTTextStorage, @selector(mc_writeSelectionToPasteboard:type:), + method_getImplementation(originalMethod), + method_getTypeEncoding(swizzledMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } +} + +@implementation NSTextView (MCIDEConsoleTextView) + ++ (void)load { + swizzleIDEConsoleTextView(); +} + +// rewrite this method to avoid copy ANSI escape codes to pasteboard +- (BOOL)mc_writeSelectionToPasteboard:(NSPasteboard *)pboard type:(NSString *)type { + + if ([self mc_writeSelectionToPasteboard:pboard type:type]) { + NSString *text = [pboard stringForType:type]; + NSArray *matches = [escCharPattern() matchesInString:text options:0 range:NSMakeRange(0, text.length)]; + for (NSInteger idx = matches.count - 1; idx >= 0; --idx) { + NSTextCheckingResult *result = matches[idx]; + text = [text stringByReplacingCharactersInRange:result.range withString:@""]; + } + return [pboard setString:text forType:type]; + } + return NO; +} + +@end diff --git a/MCLog/MCLog-Info.plist b/MCLog/MCLog-Info.plist index 9554b3e..aa7e393 100644 --- a/MCLog/MCLog-Info.plist +++ b/MCLog/MCLog-Info.plist @@ -9,7 +9,7 @@ CFBundleIconFile CFBundleIdentifier - io.michaelchen.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/MCLog/MCLog-Prefix.pch b/MCLog/MCLog-Prefix.pch index a33d4a8..2259281 100644 --- a/MCLog/MCLog-Prefix.pch +++ b/MCLog/MCLog-Prefix.pch @@ -20,6 +20,15 @@ // Defaults #define MCLOG_FLAG "MCLOG_FLAG" + +// maybe we should use const value define #define kTagSearchField 99 -#define MCLogger(fmt, ...) NSLog((@"[MCLog] %s(Line:%d) " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) \ No newline at end of file +#define MCLogger(fmt, ...) NSLog((@"[MCLog] %s(Line:%d) " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) + +#define stringify(obj) \ + [(obj) isKindOfClass:[NSString class]] \ + ? (obj) \ + : [(obj) isKindOfClass:[NSNumber class]] \ + ? [(NSNumber *)(obj) stringValue] \ + : [(id)(obj) description] diff --git a/MCLog/MCLog.h b/MCLog/MCLog.h index 13f94f8..0755edc 100644 --- a/MCLog/MCLog.h +++ b/MCLog/MCLog.h @@ -8,8 +8,14 @@ @import Foundation; +@class MCOrderedMap; @interface MCLog : NSObject + + (void)pluginDidLoad:(NSBundle *)bundle; + ++ (NSMutableDictionary *)consoleItemsMap; + ++ (NSMutableDictionary *)filterPatternsMap; @end diff --git a/MCLog/MCLog.m b/MCLog/MCLog.m index 7ddafe7..295600c 100644 --- a/MCLog/MCLog.m +++ b/MCLog/MCLog.m @@ -6,810 +6,232 @@ // Copyright (c) 2014年 Yuhua Chen. All rights reserved. // +#include +#import +#import +#import "HHTimer.h" +#import "MCDVTTextStorage.h" #import "MCLog.h" +#import "MCOrderedMap.h" #import "MCXcodeHeaders.h" -#import "MCDVTTextStorage.h" -#import -#include - - -@class MCLogIDEConsoleArea; - -static NSMutableDictionary *OriginConsoleItemsMap = nil; -//static NSSearchField *SearchField = nil; -static NSMutableDictionary *SearchPatternsDic = nil; - -NSSearchField *getSearchField(id consoleArea); -NSString *hash(id obj); - -NSArray *backtraceStack(); -void swizzleDVTTextStorage(); -void hookIDEConsoleAdaptor(); -void hookIDEConsoleArea(); -void hookIDEConsoleItem(); -NSRegularExpression * logItemPrefixPattern(); -NSRegularExpression * escCharPattern(); - - -typedef NS_ENUM(NSUInteger, MCLogLevel) { - MCLogLevelVerbose = 0x1000, - MCLogLevelInfo, - MCLogLevelWarn, - MCLogLevelError -}; - - -///////////////////////////////////////////////////////////////////////////////////// -@interface MCOrderedMap : NSObject -@property (nonatomic, strong) NSMutableOrderedSet *keys; -@property (nonatomic, strong) NSMutableArray *items; - -- (void)addObject:(id)object forKey:(id)key; -- (id)removeObjectForKey:(id)key; -- (id)objectForKey:(id)key; -- (BOOL)containsObjectForKey:(id)key; -- (NSArray *)OrderedKeys; -- (NSArray *)orderedItems; -@end - -#define verifyMap() \ -do{\ -NSAssert(self.keys.count == self.items.count, @"keys and items are not matched!");\ -}while(0) - -@implementation MCOrderedMap - -- (instancetype)init { - self = [super init]; - if (self) { - _keys = [NSMutableOrderedSet orderedSet]; - _items = [NSMutableArray array]; - } - return self; -} - -- (id)objectForKey:(id)key { - verifyMap(); - NSUInteger keyIndex = [self.keys indexOfObject:key]; - if (keyIndex != NSNotFound) { - return self.items[keyIndex]; - } - return nil; -} - -- (void)addObject:(id)object forKey:(id)key { - NSParameterAssert(key != nil && object != nil); - verifyMap(); - NSUInteger keyIndex = [self.keys indexOfObject:key]; - if (keyIndex == NSNotFound) { - [self.keys addObject:key]; - [self.items addObject:object]; - } else { - [self.items replaceObjectAtIndex:keyIndex withObject:object]; - } -} - -- (id)removeObjectForKey:(id)key { - verifyMap(); - NSUInteger keyIndex = [self.keys indexOfObject:key]; - if (keyIndex != NSNotFound) { - [self.keys removeObject:key]; - id object = self.items[keyIndex]; - [self.items removeObjectAtIndex:keyIndex]; - return object; - } - return nil; -} - -- (BOOL)containsObjectForKey:(id)key { - verifyMap(); - return [self.keys containsObject:key]; -} - - -- (NSArray *)OrderedKeys { - verifyMap(); - return [[self.keys array] copy]; -} - -- (NSArray *)orderedItems { - verifyMap(); - return [self.items copy]; -} - -@end - +#import "NSView+MCLog.h" +#import "MCIDEConsoleItem.h" +#import "NSSearchField+MCLog.h" +#import "Utils.h" +#import "MethodSwizzle.h" +#import "MCLogIDEConsoleArea.h" -//////////////////////////////////////////////////////////////////////////////////// -#pragma mark - NSSearchField (MCLog) -@interface NSSearchField (MCLog) -@property (nonatomic, strong) MCLogIDEConsoleArea *consoleArea; -@property (nonatomic, strong) NSTextView *consoleTextView; -@end +NS_ASSUME_NONNULL_BEGIN -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]; -} +#pragma mark - method swizzle +@implementation MCLog -- (void)updateItemAttribute:(id)item -{ - NSError *error = nil; - NSString *logText = [item valueForKey:@"content"]; - if ([[item valueForKey:@"error"] boolValue]) { - logText = [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), logText]; - [item setValue:logText forKey:@"content"]; - return; - } - - if (![[item valueForKey:@"output"] boolValue] || [[item valueForKey:@"outputRequestedByUser"] boolValue]) { - return; - } ++ (void)load { + NSLog(@"%s, env: %s", __PRETTY_FUNCTION__, getenv(MCLOG_FLAG)); - if (!logText) { - return; - } - - NSRange prefixRange = [logItemPrefixPattern() rangeOfFirstMatchInString:logText options:0 range:NSMakeRange(0, logText.length)]; - if (prefixRange.location != 0 || logText.length <= prefixRange.length) { + if (getenv(MCLOG_FLAG) && !strcmp(getenv(MCLOG_FLAG), "YES")) { + // alreay installed plugin 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]; - } else { - static NSMutableArray *extraErrorPatterns = nil; - if (extraErrorPatterns == nil) { - extraErrorPatterns = [NSMutableArray array]; - for (NSString *patternStr in @[ - @"^\\s*\\*\\*\\* Terminating app due to uncaught exception '.+', reason: '[\\s\\S]+'\\n*\\*\\*\\* First throw call stack:\\s*\\n", - @"^\\s*(\\+|-)\\[[a-zA-Z_]\\w*\\s[a-zA-Z_]\\w*[(:([a-zA-Z_]\\w*)?)]*\\]: unrecognized selector sent to (class|instance) [\\dxXa-fA-F]+", - @"^\\s*\\*\\*\\* Assertion failure in (\\+|-)\\[[a-zA-Z_]\\w*\\s[a-zA-Z_]\\w*[(:([a-zA-Z_]\\w*)?)]*\\],", - @"^\\s*\\*\\*\\* Terminating app due to uncaught exception of class '[a-zA-Z_]\\w+'" - ]) { - NSRegularExpression *r = [NSRegularExpression regularExpressionWithPattern:patternStr options:0 error:&error]; - if (!r) { - MCLogger(@"ERROR:%@", error); - continue; - } - [extraErrorPatterns addObject:r]; - } - } - for (NSRegularExpression *r in extraErrorPatterns) { - if ([r matchesInString:originalContent options:0 range:NSMakeRange(0, originalContent.length)].count > 0) { - content = [NSString stringWithFormat:(LC_ESC @"[31m%@" LC_RESET), content]; - break; - } - } - } - - [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; - } - - MCOrderedMap *originConsoleItems = OriginConsoleItemsMap[hash(self)]; - if (!originConsoleItems) { - originConsoleItems = [[MCOrderedMap alloc] init]; - } - - 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. - if (![originConsoleItems containsObjectForKey:@([obj timestamp])]) { - [originConsoleItems addObject:obj forKey:@([obj timestamp])]; - } - [OriginConsoleItemsMap setObject:originConsoleItems forKey:hash(self)]; - } - return NO; - } - - if (!searchField) { - return YES; - } - - - // store all console items. - if (![originConsoleItems containsObjectForKey:@([obj timestamp])]) { - [originConsoleItems addObject:obj forKey:@([obj timestamp])]; - } - [OriginConsoleItemsMap setObject:originConsoleItems forKey:hash(self)]; - - if (searchField.stringValue.length == 0) { - return YES; - } - - // Remove prefix log pattern - NSString *content = [obj content]; - NSRange range = NSMakeRange(0, content.length); - - NSRegularExpression *logRegex = logItemPrefixPattern(); - content = [logRegex stringByReplacingMatchesInString:content options:(NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators) range:range withTemplate:@""]; - - - if (SearchPatternsDic == nil) { - SearchPatternsDic = [NSMutableDictionary dictionary]; - } - - // Test with user's regex pattern - NSError *error; - NSRegularExpression *regex = SearchPatternsDic[hash(self)]; - if (regex == nil || ![regex.pattern isEqualToString:searchField.stringValue]) { - regex = [NSRegularExpression regularExpressionWithPattern:searchField.stringValue - options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators) - error:&error]; - if (regex == nil) { - // display all if with regex is error - MCLogger(@"error:%@", error); - return YES; - } - SearchPatternsDic[hash(self)] = regex; - } - - range = NSMakeRange(0, content.length); - 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)]; + setenv(MCLOG_FLAG, "YES", 0); } -@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 dispatch_queue_t buffer_queue() { - static dispatch_queue_t mclog_buffer_queue = nil; ++ (void)pluginDidLoad:(NSBundle *)bundle { + MCLogger(@"%s, %@", __PRETTY_FUNCTION__, bundle); + static id sharedPlugin = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - mclog_buffer_queue = dispatch_queue_create("io.michaelchen.mclog.buffer-queue", DISPATCH_QUEUE_SERIAL); + sharedPlugin = [[self alloc] init]; }); - - return mclog_buffer_queue; -} - -@interface NSObject (MCIDEConsoleAdaptor) - -- (void)setUnprocessedOutputInfo:(NSDictionary *)outputInfo; -- (NSDictionary *)unprocessedOutputInfo; -- (void)MCOutputUnprocessedBuffer; - -@end - -@implementation NSObject (MCIDEConsoleAdaptor) - -- (void)setUnprocessedOutputInfo:(NSDictionary *)outputInfo -{ - objc_setAssociatedObject(self, &kUnProcessedOutputKey, outputInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (NSDictionary *)unprocessedOutputInfo -{ - return objc_getAssociatedObject(self, &kUnProcessedOutputKey); -} - -- (void)MCOutputUnprocessedBuffer -{ - NSDictionary *unprocessedOutputInfo = self.unprocessedOutputInfo; - if (unprocessedOutputInfo) { - [self setUnprocessedOutputInfo:nil]; - originalOutputForStandardOutputIMP(self, _cmd, [unprocessedOutputInfo[@"content"] stringValue], - [unprocessedOutputInfo[@"isPrompt"] boolValue], - [unprocessedOutputInfo[@"isOutputRequestedByUser"] boolValue]); - } } -@end - - -@implementation MCIDEConsoleAdaptor - -- (void)outputForStandardOutput:(id)arg1 isPrompt:(BOOL)arg2 isOutputRequestedByUser:(BOOL)arg3 -{ - NSRegularExpression *logSeperatorPattern = logItemPrefixPattern(); - - NSString *unprocessedString = self.unprocessedOutputInfo[@"content"]; - [self setUnprocessedOutputInfo:nil]; - - NSString *buffer = arg1; - if (unprocessedString.length > 0) { - buffer = [unprocessedString stringByAppendingString:arg1]; - } - - 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)]; - dispatch_async(buffer_queue(), ^{ - originalOutputForStandardOutputIMP(self, _cmd, logItemData, arg2, arg3); - }); - } - lastMatchingRange = result.range; - } - - if (lastMatchingRange.location + lastMatchingRange.length < [buffer length]) { - unprocessedString = [buffer substringFromIndex:lastMatchingRange.location]; - } - - } else { - dispatch_async(buffer_queue(), ^{ - originalOutputForStandardOutputIMP(self, _cmd, buffer, arg2, arg3); - }); - } - } else { - dispatch_async(buffer_queue(), ^{ - originalOutputForStandardOutputIMP(self, _cmd, arg1, arg2, arg3); - }); - } - - if (unprocessedString.length > 0) { - [self setUnprocessedOutputInfo:@{@"content": unprocessedString, - @"isPrompt": @(arg2), - @"isOutputRequestedByUser": @(arg3)}]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.005 * NSEC_PER_SEC)), buffer_queue(), ^{ - [self MCOutputUnprocessedBuffer]; - }); - } - -} - -@end - - -/////////////////////////////////////////////////////////////////////////////////////////// - -@interface MCLog () -@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; - } - - swizzleDVTTextStorage(); - hookIDEConsoleAdaptor(); - hookIDEConsoleArea(); - hookIDEConsoleItem(); - - OriginConsoleItemsMap = [NSMutableDictionary dictionary]; - setenv(MCLOG_FLAG, "YES", 0); ++ (NSMutableDictionary *)consoleItemsMap { + static NSMutableDictionary *dict = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dict = [NSMutableDictionary dictionary]; + }); + return dict; } -+ (void)pluginDidLoad:(NSBundle *)bundle -{ - NSLog(@"%s, %@", __PRETTY_FUNCTION__, bundle); - static id sharedPlugin = nil; ++ (NSMutableDictionary *)filterPatternsMap { + static NSMutableDictionary *dict = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedPlugin = [[self alloc] init]; + dict = [NSMutableDictionary dictionary]; }); + return dict; } -- (void)dealloc -{ +- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (id)init -{ +- (id)init { self = [super init]; if (self) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activate:) name:@"IDEControlGroupDidChangeNotificationName" object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(activate:) + name:@"IDEControlGroupDidChangeNotificationName" + 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; -} +#pragma mark - init UI -- (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]; +- (BOOL)addCustomViews { + NSView *contentView = [[NSApp mainWindow] contentView]; + NSTextView *consoleTextView = [contentView descendantViewByClassName:@"IDEConsoleTextView"]; if (!consoleTextView) { return NO; } + DVTTextStorage *textStorage = [consoleTextView valueForKey:@"textStorage"]; + if ([textStorage respondsToSelector:@selector(setConsoleStorage:)]) { + [textStorage setConsoleStorage:YES]; + } - DVTTextStorage *textStorage = [consoleTextView valueForKey:@"textStorage"]; - if ([textStorage respondsToSelector:@selector(setConsoleStorage:)]) { - [textStorage setConsoleStorage:YES]; - } - - contentView = [self getParantViewByClassName:@"DVTControllerContentView" andView:consoleTextView]; - NSView *scopeBarView = [self getViewByClassName:@"DVTScopeBarView" andContainerView:contentView]; + contentView = [consoleTextView ancestralViewByClassName:@"DVTControllerContentView"]; + NSView *scopeBarView = [contentView descendantViewByClassName:@"DVTScopeBarView"]; if (!scopeBarView) { return NO; } - NSButton *button = nil; + + [self addLogLevelButtonItemsAt:scopeBarView defaultLogLevel:[[consoleTextView valueForKey:@"logMode"] intValue]]; + [self addLogFilterPatternTextFieldAt:scopeBarView associateWith:consoleTextView]; + + + return YES; +} + +- (BOOL)addLogLevelButtonItemsAt:(NSView *)scopeBarView defaultLogLevel:(MCLogLevel)level{ 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; + for (__kindof NSView *subView in scopeBarView.subviews) { + if ([[subView className] isEqualToString:@"NSPopUpButton"]) { + filterButton = subView; + break; } } - - if (!button) { - return NO; - } - - if(filterButton) { + + 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 (level >= MCLogLevelVerbose && level <= MCLogLevelError) { + [filterButton selectItemWithTag:level]; + } } - - NSInteger selectedItem = [filterButton indexOfItemWithTag:[[consoleTextView valueForKey:@"logMode"] intValue]]; - if (selectedItem < 0 || selectedItem >= [filterButton numberOfItems]) { - [filterButton selectItemAtIndex:0]; - } - + return YES; +} + +- (void)filterPopupButton:(NSPopUpButton *)popupButton addItemWithTitle:(NSString *)title tag:(NSUInteger)tag { + [popupButton addItemWithTitle:title]; + [popupButton itemAtIndex:popupButton.numberOfItems - 1].tag = tag; +} + +- (BOOL)addLogFilterPatternTextFieldAt:(NSView *)scopeBarView associateWith:(NSTextView *)consoleTextView { if ([scopeBarView viewWithTag:kTagSearchField]) { return YES; } + NSButton *button = nil; + for (__kindof NSView *subView in scopeBarView.subviews) { + if ([[subView className] isEqualToString:@"NSButton"]) { + button = subView; + break; + } + } + 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]; + 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.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]; + [[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 -{ +- (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; + + NSTextView *consoleTextView = searchField.consoleTextView; MCLogIDEConsoleArea *consoleArea = searchField.consoleArea; + NSString *cachedKey = hash(consoleArea); + if (cachedKey == nil) { + return; + } + + NSString *lastFilterText = nil; + id filterPattern = [self.class filterPatternsMap][cachedKey]; + if ([filterPattern isKindOfClass:[NSRegularExpression class]]) { + lastFilterText = ((NSRegularExpression *) filterPattern).pattern; + } + lastFilterText = lastFilterText.length == 0 ? @"" : lastFilterText; - // get rid of the annoying 'undeclared selector' warning + if ([searchField.stringValue isEqualToString:lastFilterText]) { + return; + } + +// 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]; } - NSString *cachedKey = hash(consoleArea); - if (cachedKey) { - NSArray *sortedItems = [OriginConsoleItemsMap[hash(consoleArea)] orderedItems]; - - if ([consoleArea respondsToSelector:@selector(_appendItems:)]) { - [consoleArea performSelector:@selector(_appendItems:) withObject:sortedItems]; - } - - [SearchPatternsDic removeObjectForKey:hash(consoleArea)]; - } -#pragma clang diagnostic pop -} - -- (void)activate:(NSNotification *)notification -{ - [self addCustomViews]; -} - -@end - -#pragma mark - method hookers - -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); - 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 swizzleDVTTextStorage() -{ - Class DVTTextStorage = NSClassFromString(@"DVTTextStorage"); - Method fixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(fixAttributesInRange:)); - Method swizzledFixAttributesInRange = class_getInstanceMethod(DVTTextStorage, @selector(mc_fixAttributesInRange:)); - - BOOL didAddMethod = class_addMethod(DVTTextStorage, @selector(fixAttributesInRange:), method_getImplementation(swizzledFixAttributesInRange), method_getTypeEncoding(swizzledFixAttributesInRange)); - if (didAddMethod) { - class_replaceMethod(DVTTextStorage, @selector(mc_fixAttributesInRange:), method_getImplementation(fixAttributesInRange), method_getTypeEncoding(swizzledFixAttributesInRange)); - } else { - method_exchangeImplementations(fixAttributesInRange, swizzledFixAttributesInRange); - } -} - -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 - -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); - } + static SEL selector = nil; + static BOOL canResponse = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + selector = @selector(_appendItems:); + canResponse = [consoleArea respondsToSelector:selector]; + }); + if (canResponse) { + NSArray *sortedItems = [[self.class consoleItemsMap][cachedKey] orderedItems]; + objc_msgSend(consoleArea, selector, sortedItems); } - return pattern; +#pragma clang diagnostic pop } -NSSearchField *getSearchField(id consoleArea) -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - if (![consoleArea respondsToSelector:@selector(scopeBarView)]) { - return nil; - } - - NSView *scopeBarView = [consoleArea performSelector:@selector(scopeBarView) withObject:nil]; - return [scopeBarView viewWithTag:kTagSearchField]; -#pragma clang diagnositc pop -} -NSString *hash(id obj) -{ - if (!obj) { - return nil; - } - - return [NSString stringWithFormat:@"%lx", (long)obj]; +- (void)activate:(NSNotification *)notification { + [self addCustomViews]; } +@end -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; -} +NS_ASSUME_NONNULL_END diff --git a/MCLog/MCLogIDEConsoleArea.h b/MCLog/MCLogIDEConsoleArea.h new file mode 100644 index 0000000..6c72b22 --- /dev/null +++ b/MCLog/MCLogIDEConsoleArea.h @@ -0,0 +1,17 @@ +// +// MCLogIDEConsoleArea.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +@interface MCLogIDEConsoleArea : NSViewController + +- (BOOL)_shouldAppendItem:(id)obj; +- (void)_clearText; + +@end + diff --git a/MCLog/MCLogIDEConsoleArea.m b/MCLog/MCLogIDEConsoleArea.m new file mode 100644 index 0000000..5e17a83 --- /dev/null +++ b/MCLog/MCLogIDEConsoleArea.m @@ -0,0 +1,140 @@ +// +// MCLogIDEConsoleArea.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "MCLogIDEConsoleArea.h" +#import "Utils.h" +#import "NSSearchField+MCLog.h" +#import "MCOrderedMap.h" +#import "MCLog.h" +#import "MCIDEConsoleItem.h" +#import "MethodSwizzle.h" +#import + + +@implementation MCLogIDEConsoleArea + ++ (void)load { + Class clazz = NSClassFromString(@"IDEConsoleArea"); + IMP hookedShouldAppendItemIMP = class_getMethodImplementation([MCLogIDEConsoleArea class], + @selector(_shouldAppendItem:)); + + [MethodSwizzleHelper swizzleMethodForClass:clazz + selector:@selector(_shouldAppendItem:) + replacementIMP:hookedShouldAppendItemIMP + isClassMethod:NO]; + + IMP hookClearTextIMP = class_getMethodImplementation([MCLogIDEConsoleArea class], @selector(_clearText)); + [MethodSwizzleHelper swizzleMethodForClass:clazz + selector:@selector(_clearText) + replacementIMP:hookClearTextIMP + isClassMethod:NO]; +} + +- (BOOL)_shouldAppendItem:(id)obj { + static IMP originalIMP = nil; + if (originalIMP == nil) { + Class clazz = NSClassFromString(@"IDEConsoleArea"); + SEL selector = @selector(_shouldAppendItem:); + originalIMP = [MethodSwizzleHelper originalIMPForClass:clazz selector:selector]; + } + + if (obj == nil) { + return [originalIMP(self, _cmd, obj) boolValue]; + } + + NSMutableDictionary *consoleItemsMap = [MCLog consoleItemsMap]; + NSString *consoleItemsKey = hash(self); + + MCOrderedMap *originConsoleItems = consoleItemsMap[consoleItemsKey]; + if (!originConsoleItems) { + originConsoleItems = [[MCOrderedMap alloc] init]; + originConsoleItems.maximumnItemsCount = 65535; + consoleItemsMap[consoleItemsKey] = originConsoleItems; + } + [originConsoleItems addObject:obj forKey:@([obj timestamp])]; + + BOOL isInputItem = [[obj valueForKey:@"input"] boolValue]; + BOOL isPromptItem = [[obj valueForKey:@"prompt"] boolValue]; + BOOL isoutputRequestByUser = [[obj valueForKey:@"outputRequestedByUser"] boolValue]; + BOOL isDebuggerAdaptor = [[obj valueForKey:@"adaptorType"] hasSuffix:@".Debugger"]; + + NSInteger filterMode = [[self valueForKey:@"filterMode"] intValue]; + BOOL shouldShowLogLevel = YES; + if (filterMode >= MCLogLevelVerbose) { + shouldShowLogLevel = + [obj logLevel] >= filterMode || isInputItem || isPromptItem || isoutputRequestByUser || isDebuggerAdaptor; + } else { + shouldShowLogLevel = [originalIMP(self, _cmd, obj) boolValue]; + } + + if (!shouldShowLogLevel) { + return NO; + } + + NSSearchField *searchField = getSearchField(self); + if (searchField == nil) { + return YES; + } + if (!searchField.consoleArea) { + searchField.consoleArea = (MCLogIDEConsoleArea *)self; + } + + if (searchField.stringValue.length == 0) { + [[MCLog filterPatternsMap] removeObjectForKey:consoleItemsKey]; + return YES; + } + + // Remove prefix log pattern + NSString *content = [obj content]; + NSRange range = NSMakeRange(0, content.length); + + NSRegularExpression *logRegex = logItemPrefixPattern(); + content = [logRegex stringByReplacingMatchesInString:content + options:(NSRegularExpressionCaseInsensitive | + NSRegularExpressionDotMatchesLineSeparators) + range:range + withTemplate:@""]; + + // Test with user's regex pattern + NSRegularExpression *regex = [MCLog filterPatternsMap][consoleItemsKey]; + NSError *error; + if (regex == nil || ![regex.pattern isEqualToString:searchField.stringValue]) { + regex = [NSRegularExpression regularExpressionWithPattern:searchField.stringValue + options:(NSRegularExpressionCaseInsensitive | + NSRegularExpressionDotMatchesLineSeparators) + error:&error]; + if (regex == nil) { + // display all if with regex is error + MCLogger(@"error:%@", error); + return YES; + } + [MCLog filterPatternsMap][consoleItemsKey] = regex; + } + + range = NSMakeRange(0, content.length); + NSArray *matches = [regex matchesInString:content options:0 range:range]; + if ([matches count] > 0 || isInputItem || isPromptItem || isoutputRequestByUser || isDebuggerAdaptor) { + return YES; + } + + return NO; +} + +- (void)_clearText +{ + static IMP originalIMP = nil; + if (originalIMP == nil) { + Class clazz = NSClassFromString(@"IDEConsoleArea"); + SEL selector = @selector(_clearText); + originalIMP = [MethodSwizzleHelper originalIMPForClass:clazz selector:selector]; + } + + originalIMP(self, _cmd); + [[MCLog consoleItemsMap] removeObjectForKey:hash(self)]; +} +@end diff --git a/MCLog/MCOrderedMap.h b/MCLog/MCOrderedMap.h new file mode 100644 index 0000000..f805521 --- /dev/null +++ b/MCLog/MCOrderedMap.h @@ -0,0 +1,30 @@ +// +// MCOrderMap.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MCOrderedMap<__covariant KeyType, __covariant ObjectType> : NSObject + +@property(nonatomic) NSUInteger maximumnItemsCount; + +- (void)addObject:(ObjectType)object forKey:(KeyType)key; + +- (ObjectType)removeObjectForKey:(KeyType)key; + +- (ObjectType)objectForKey:(KeyType)key; + +- (BOOL)containsObjectForKey:(KeyType)key; + +- (NSArray *)OrderedKeys; +- (NSArray *)orderedItems; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/MCLog/MCOrderedMap.m b/MCLog/MCOrderedMap.m new file mode 100644 index 0000000..1e49aa2 --- /dev/null +++ b/MCLog/MCOrderedMap.m @@ -0,0 +1,105 @@ +// +// MCOrderMap.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "MCOrderedMap.h" + +#define verifyMap() \ +do{\ + NSAssert(self.keys.count == self.items.count, @"keys and items are not matched!");\ +}while(NO) + +NS_ASSUME_NONNULL_BEGIN + +@interface MCOrderedMap<__covariant KeyType, __covariant ObjectType> () +@property (nonatomic, strong) NSMutableOrderedSet *keys; +@property (nonatomic, strong) NSMutableArray *items; +@end + +@implementation MCOrderedMap + +- (instancetype)init { + self = [super init]; + if (self) { + _keys = [NSMutableOrderedSet orderedSet]; + _items = [NSMutableArray array]; + } + return self; +} + +- (id)objectForKey:(id)key { + verifyMap(); + NSUInteger keyIndex = [self.keys indexOfObject:key]; + if (keyIndex != NSNotFound) { + return self.items[keyIndex]; + } + return nil; +} + +- (void)addObject:(id)object forKey:(id)key { + NSParameterAssert(key != nil && object != nil); + @synchronized(self) { + verifyMap(); + //[self removeHeadItems]; + NSUInteger keyIndex = [self.keys indexOfObject:key]; + if (keyIndex == NSNotFound) { + [self.keys addObject:key]; + [self.items addObject:object]; + } else { + [self.items replaceObjectAtIndex:keyIndex withObject:object]; + } + } +} + +- (id)removeObjectForKey:(id)key { + @synchronized(self) { + verifyMap(); + NSUInteger keyIndex = [self.keys indexOfObject:key]; + if (keyIndex != NSNotFound) { + [self.keys removeObject:key]; + id object = self.items[keyIndex]; + [self.items removeObjectAtIndex:keyIndex]; + return object; + } + } + return nil; +} + +- (BOOL)containsObjectForKey:(id)key { + verifyMap(); + return [self.keys containsObject:key]; +} + + +- (NSArray *)OrderedKeys { + verifyMap(); + return [[self.keys array] copy]; +} + +- (NSArray *)orderedItems { + verifyMap(); + return [self.items copy]; +} + +- (void)removeHeadItems { + if (self.maximumnItemsCount == 0) { + return; + } + + NSInteger itemsToRemove = self.keys.count - self.maximumnItemsCount; + if (itemsToRemove <= 0) { + return; + } + NSRange range = NSMakeRange(0, itemsToRemove); + [self.keys removeObjectsInRange:range]; + [self.items removeObjectsInRange:range]; + +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/MCLog/MCXcodeHeaders.h b/MCLog/MCXcodeHeaders.h index 0534aed..54040f0 100644 --- a/MCLog/MCXcodeHeaders.h +++ b/MCLog/MCXcodeHeaders.h @@ -10,4 +10,7 @@ @import Cocoa; @interface DVTTextStorage : NSTextStorage -@end \ No newline at end of file +@end + +@interface IDEConsoleTextView : NSTextView +@end diff --git a/MCLog/MethodSwizzle.h b/MCLog/MethodSwizzle.h new file mode 100644 index 0000000..14abe3a --- /dev/null +++ b/MCLog/MethodSwizzle.h @@ -0,0 +1,33 @@ +// +// MethodSwizzle.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SwizzleContext : NSObject + +@property(nonatomic, weak) id object; +@property(nonatomic) SEL selector; +@property(nonatomic) IMP originalIMP; +@property(nonatomic) IMP swizzleIMP; + +@end + +@interface MethodSwizzleHelper : NSObject + ++ (BOOL)swizzleMethodForClass:(Class)clazz + selector:(SEL)selector + replacementIMP:(IMP)imp + isClassMethod:(BOOL)isClassMethod; + ++ (IMP)originalIMPForClass:(Class)object selector:(SEL)selector; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MCLog/MethodSwizzle.m b/MCLog/MethodSwizzle.m new file mode 100644 index 0000000..f202b0f --- /dev/null +++ b/MCLog/MethodSwizzle.m @@ -0,0 +1,67 @@ +// +// MethodSwizzle.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import +#import "MethodSwizzle.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SwizzleContext +@end + +@implementation MethodSwizzleHelper + ++ (NSMutableDictionary *)swizzleMethods { + static NSMutableDictionary *kSwizzleMethods; + + if (kSwizzleMethods == nil) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + kSwizzleMethods = [NSMutableDictionary dictionary]; + }); + } + return kSwizzleMethods; +} + ++ (NSString *)contextKeyForClass:(Class)clazz selector:(SEL)selector { + return [NSString stringWithFormat:@"%@:[%@]", NSStringFromClass(clazz), NSStringFromSelector(selector)]; +} + ++ (BOOL)swizzleMethodForClass:(Class)clazz + selector:(SEL)selector + replacementIMP:(IMP)imp + isClassMethod:(BOOL)isClassMethod { + Method method = isClassMethod ? class_getClassMethod(clazz, selector) : class_getInstanceMethod(clazz, selector); + if (method == nil) { + return NO; + } + + IMP originalIMP = method_setImplementation(method, imp); + + SwizzleContext *context = [[SwizzleContext alloc] init]; + context.object = clazz; + context.selector = selector; + context.originalIMP = originalIMP; + context.swizzleIMP = imp; + + [self swizzleMethods][[self contextKeyForClass:clazz selector:selector]] = context; + + return YES; +} + ++ (IMP)originalIMPForClass:(Class)object selector:(SEL)selector { + SwizzleContext *context = [self swizzleMethods][[self contextKeyForClass:object selector:selector]]; + if (context == nil) { + return nil; + } + return context.originalIMP; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/MCLog/NSSearchField+MCLog.h b/MCLog/NSSearchField+MCLog.h new file mode 100644 index 0000000..ad9c2c0 --- /dev/null +++ b/MCLog/NSSearchField+MCLog.h @@ -0,0 +1,18 @@ +// +// NSSearchField+MCLog.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +@class MCLogIDEConsoleArea; +@interface NSSearchField (MCLog) + +@property (nonatomic, weak) MCLogIDEConsoleArea *consoleArea; +@property (nonatomic, weak) NSTextView *consoleTextView; + +@end + diff --git a/MCLog/NSSearchField+MCLog.m b/MCLog/NSSearchField+MCLog.m new file mode 100644 index 0000000..7f6e18b --- /dev/null +++ b/MCLog/NSSearchField+MCLog.m @@ -0,0 +1,45 @@ +// +// NSSearchField+MCLog.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "NSSearchField+MCLog.h" +#import "ALAssociatedWeakObject.h" +#import "MCLogIDEConsoleArea.h" +#import + +static const void *kMCLogConsoleTextViewKey; +static const void *kMCLogIDEConsoleAreaKey; + +@implementation NSSearchField (MCLog) + +- (void)setConsoleArea:(MCLogIDEConsoleArea *)consoleArea { + objc_setAssociatedObject(self, &kMCLogIDEConsoleAreaKey, consoleArea, OBJC_ASSOCIATION_ASSIGN); + __weak typeof(self) weakSelf = self; + [consoleArea mc_runAtDealloc:^{ + typeof(weakSelf) self = weakSelf; + objc_setAssociatedObject(self, &kMCLogIDEConsoleAreaKey, nil, OBJC_ASSOCIATION_ASSIGN); + }]; +} + +- (MCLogIDEConsoleArea *)consoleArea { + return objc_getAssociatedObject(self, &kMCLogIDEConsoleAreaKey); +} + +- (void)setConsoleTextView:(NSTextView *)consoleTextView { + objc_setAssociatedObject(self, &kMCLogConsoleTextViewKey, consoleTextView, OBJC_ASSOCIATION_ASSIGN); + __weak typeof(self) weakSelf = self; + [consoleTextView mc_runAtDealloc:^{ + typeof(weakSelf) self = weakSelf; + objc_setAssociatedObject(self, &kMCLogConsoleTextViewKey, nil, OBJC_ASSOCIATION_ASSIGN); + }]; +} + +- (NSTextView *)consoleTextView { + return objc_getAssociatedObject(self, &kMCLogConsoleTextViewKey); +} + +@end diff --git a/MCLog/NSView+MCLog.h b/MCLog/NSView+MCLog.h new file mode 100644 index 0000000..787293a --- /dev/null +++ b/MCLog/NSView+MCLog.h @@ -0,0 +1,19 @@ +// +// NSView+MCLog.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@interface NSView (MCLog) + +- (nullable __kindof NSView *)descendantViewByClassName:(NSString *)className; + +- (nullable __kindof NSView *)ancestralViewByClassName:(NSString *)className; +@end + +NS_ASSUME_NONNULL_END diff --git a/MCLog/NSView+MCLog.m b/MCLog/NSView+MCLog.m new file mode 100644 index 0000000..8593713 --- /dev/null +++ b/MCLog/NSView+MCLog.m @@ -0,0 +1,44 @@ +// +// NSView+MCLog.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "NSView+MCLog.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSView (MCLog) + +- (nullable __kindof NSView *)descendantViewByClassName:(NSString *)className{ + Class class = NSClassFromString(className); + + for (NSView *subView in self.subviews) { + if ([subView isKindOfClass:class]) { + return subView; + } else { + NSView *view = [subView descendantViewByClassName:className]; + if ([view isKindOfClass:class]) { + return view; + } + } + } + return nil; +} + +- (nullable __kindof NSView *)ancestralViewByClassName:(NSString *)className { + if ([stringify(className) length] == 0) return nil; + NSView *superView = self.superview; + while (superView) { + if ([[superView className] isEqualToString:className]) { + return superView; + } + superView = superView.superview; + } + return nil; +} +@end + +NS_ASSUME_NONNULL_END diff --git a/MCLog/PluginConfigs.h b/MCLog/PluginConfigs.h new file mode 100644 index 0000000..7c192b8 --- /dev/null +++ b/MCLog/PluginConfigs.h @@ -0,0 +1,45 @@ +// +// PluginConfigs.h +// MCLog +// +// Created by Alex Lee on 1/7/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + + +////////////////////////// configurations ///////////////////////// +// clang-format off +// color tables ANSI Codes +//bright colors (default colors) text color background color +extern const UInt32 kBrightBlack; // 30 40 +extern const UInt32 kBrightRed; // 31 41 +extern const UInt32 kBrightGreen; // 32 42 +extern const UInt32 kBrightYellow; // 33 43 +extern const UInt32 kBrightBlue; // 34 44 +extern const UInt32 kBrightPurple; // 35 45 +extern const UInt32 kBrightCyan; // 36 46 +extern const UInt32 kBrightWhite; // 37 47 + +// normal (dark) colors (use ANSI code:2 to enable it.) +extern const UInt32 kDarkBlack; +extern const UInt32 kDarkRed; +extern const UInt32 kDarkGreen; +extern const UInt32 kDarkYellow; +extern const UInt32 kDarkBlue; +extern const UInt32 kDarkPurple; +extern const UInt32 kDarkCyan; +extern const UInt32 kDarkWhite; +// clang-format on + + +extern NSColor *ANSICodeToNSColor(NSInteger value, BOOL useBrightColor); +extern NSInteger NSColorToANSICode(NSColor *color); + +extern inline NSDictionary *errorLogAttributes(); +extern inline NSDictionary *warningLogAttributes(); +extern inline NSDictionary *infoLogAttributes(); +extern inline NSDictionary *verboseLogAttributes(); + +extern BOOL useBrightColorStyle(); diff --git a/MCLog/PluginConfigs.m b/MCLog/PluginConfigs.m new file mode 100644 index 0000000..d52c2c6 --- /dev/null +++ b/MCLog/PluginConfigs.m @@ -0,0 +1,118 @@ +// +// PluginConfigs.m +// MCLog +// +// Created by Alex Lee on 1/7/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "PluginConfigs.h" + +#define NSColorWithHexRGB(rgb) \ + [NSColor colorWithCalibratedRed:((rgb) >> 16 & 0xFF) / 255.f \ + green:((rgb) >> 8 & 0xFF) / 255.f \ + blue:((rgb) & 0xFF) / 255.f \ + alpha:1.f] + +////////////////////////// configurations ///////////////////////// +// clang-format off +// color tables ANSI Codes +//bright colors (default colors) text color background color +const UInt32 kBrightBlack = 0x000000; // 30 40 +const UInt32 kBrightRed = 0xD70000; // 31 41 +const UInt32 kBrightGreen = 0x00FF00; // 32 42 +const UInt32 kBrightYellow = 0xFFFF00; // 33 43 +const UInt32 kBrightBlue = 0x2E64FF; // 34 44 +const UInt32 kBrightPurple = 0xFF00FF; // 35 45 +const UInt32 kBrightCyan = 0x00FFFF; // 36 46 +const UInt32 kBrightWhite = 0xFFFFFF; // 37 47 + +// normal (dark) colors (use ANSI code:2 to enable it.) +const UInt32 kDarkBlack = 0x000000; +const UInt32 kDarkRed = 0x800000; +const UInt32 kDarkGreen = 0x008000; +const UInt32 kDarkYellow = 0x808000; +const UInt32 kDarkBlue = 0x1C3D9B; +const UInt32 kDarkPurple = 0x800080; +const UInt32 kDarkCyan = 0x008080; +const UInt32 kDarkWhite = 0xC0C0C0; +// clang-format on + +NSArray *brightColors() { + static NSArray *kBrightColors; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + kBrightColors = @[ + NSColorWithHexRGB(kBrightBlack), + NSColorWithHexRGB(kBrightRed), + NSColorWithHexRGB(kBrightGreen), + NSColorWithHexRGB(kBrightYellow), + NSColorWithHexRGB(kBrightBlue), + NSColorWithHexRGB(kBrightPurple), + NSColorWithHexRGB(kBrightCyan), + NSColorWithHexRGB(kBrightWhite) + ]; + }); + return kBrightColors; +} + +NSArray *darkColors() { + static NSArray *kDarkColors; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + kDarkColors = @[ + NSColorWithHexRGB(kDarkBlack), + NSColorWithHexRGB(kDarkRed), + NSColorWithHexRGB(kDarkGreen), + NSColorWithHexRGB(kDarkYellow), + NSColorWithHexRGB(kDarkBlue), + NSColorWithHexRGB(kDarkPurple), + NSColorWithHexRGB(kDarkCyan), + NSColorWithHexRGB(kDarkWhite) + ]; + }); + return kDarkColors; +} + +NSColor *ANSICodeToNSColor(NSInteger value, BOOL useBrightColor) { + if ((value >= 30 && value <= 37) || (value >= 40 && value <= 47)) { + NSInteger index = value % 10; + NSArray *colors = useBrightColor ? brightColors() : darkColors(); + if (index >= 0 && index < colors.count) { + return colors[index]; + } + } + return nil; +} + +NSInteger NSColorToANSICode(NSColor *color) { + NSInteger index = [brightColors() indexOfObject:color]; + if (index == NSNotFound) { + index = [darkColors() indexOfObject:color]; + } + if (index != NSNotFound) { + return index + 30; + } + return index; +} + +inline NSDictionary *errorLogAttributes() { + return @{ NSForegroundColorAttributeName: ANSICodeToNSColor(31, useBrightColorStyle()) }; +} + +inline NSDictionary *warningLogAttributes() { + return @{ NSForegroundColorAttributeName: ANSICodeToNSColor(33, useBrightColorStyle()) }; +} + +inline NSDictionary *infoLogAttributes() { + return @{ NSForegroundColorAttributeName: ANSICodeToNSColor(32, useBrightColorStyle()) }; +} + +inline NSDictionary *verboseLogAttributes() { + return @{ NSForegroundColorAttributeName: ANSICodeToNSColor(34, useBrightColorStyle()) }; +} + +BOOL useBrightColorStyle() { + return YES; +} + diff --git a/MCLog/Utils.h b/MCLog/Utils.h new file mode 100644 index 0000000..29afdaf --- /dev/null +++ b/MCLog/Utils.h @@ -0,0 +1,18 @@ +// +// Utils.h +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import + +NSRegularExpression *logItemPrefixPattern(); +NSRegularExpression *escCharPattern(); + +NSSearchField *getSearchField(id consoleArea); + +NSArray *backtraceStack(); + +NSString *hash(id obj); diff --git a/MCLog/Utils.m b/MCLog/Utils.m new file mode 100644 index 0000000..2403b30 --- /dev/null +++ b/MCLog/Utils.m @@ -0,0 +1,80 @@ +// +// Utils.m +// MCLog +// +// Created by Alex Lee on 1/6/16. +// Copyright © 2016 Yuhua Chen. All rights reserved. +// + +#import "Utils.h" +#include + +NSRegularExpression * logItemPrefixPattern() { + static NSRegularExpression *pattern = nil; + if (pattern == nil) { + NSError *error = nil; + pattern = [NSRegularExpression + regularExpressionWithPattern:@"\\n?\\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; +} + +NSRegularExpression *escCharPattern() { + static NSRegularExpression *pattern = nil; + if (pattern == nil) { + NSError *error = nil; + pattern = + [NSRegularExpression regularExpressionWithPattern:(LC_ESC @"\\[([\\d+;]+)m") options:0 error:&error]; + if (!pattern) { + MCLogger(@"%@", error); + } + } + return pattern; +} + +NSSearchField *getSearchField(id consoleArea) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + if (![consoleArea respondsToSelector:@selector(scopeBarView)]) { + return nil; + } + + NSView *scopeBarView = [consoleArea performSelector:@selector(scopeBarView) withObject:nil]; + return [scopeBarView viewWithTag:kTagSearchField]; +#pragma clang diagnositc pop +} + + +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; +} + +NSString *hash(id obj) { + if (!obj) { + return nil; + } + + return [NSString stringWithFormat:@"%lx", (long)obj]; +} diff --git a/MCLogIOS_Demo/AppDelegate.h b/MCLogIOS_Demo/AppDelegate.h new file mode 100644 index 0000000..8fc68e6 --- /dev/null +++ b/MCLogIOS_Demo/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// MCLogIOS_Demo +// +// Created by Alex Lee on 2/25/15. +// Copyright (c) 2015 Yuhua Chen. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/MCLogIOS_Demo/AppDelegate.m b/MCLogIOS_Demo/AppDelegate.m new file mode 100644 index 0000000..090cce1 --- /dev/null +++ b/MCLogIOS_Demo/AppDelegate.m @@ -0,0 +1,52 @@ +// +// AppDelegate.m +// MCLogIOS_Demo +// +// Created by Alex Lee on 2/25/15. +// Copyright (c) 2015 Yuhua Chen. All rights reserved. +// + +#import "AppDelegate.h" + +#import "TestViewController.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + // Override point for customization after application launch. + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[TestViewController alloc] init]; + [self.window makeKeyAndVisible]; + + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/MCLogIOS_Demo/Base.lproj/LaunchScreen.xib b/MCLogIOS_Demo/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..40eecd0 --- /dev/null +++ b/MCLogIOS_Demo/Base.lproj/LaunchScreen.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MCLogIOS_Demo/Images.xcassets/AppIcon.appiconset/Contents.json b/MCLogIOS_Demo/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..36d2c80 --- /dev/null +++ b/MCLogIOS_Demo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MCLogIOS_Demo/Info.plist b/MCLogIOS_Demo/Info.plist new file mode 100644 index 0000000..eabb3ae --- /dev/null +++ b/MCLogIOS_Demo/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/MCLogIOS_Demo/TestViewController.h b/MCLogIOS_Demo/TestViewController.h new file mode 100644 index 0000000..021024c --- /dev/null +++ b/MCLogIOS_Demo/TestViewController.h @@ -0,0 +1,13 @@ +// +// TestViewController.h +// MCLog +// +// Created by Alex Lee on 2/25/15. +// Copyright (c) 2015 Yuhua Chen. All rights reserved. +// + +#import + +@interface TestViewController : UIViewController + +@end diff --git a/MCLogIOS_Demo/TestViewController.m b/MCLogIOS_Demo/TestViewController.m new file mode 100644 index 0000000..81ed12c --- /dev/null +++ b/MCLogIOS_Demo/TestViewController.m @@ -0,0 +1,217 @@ +// +// TestViewController.m +// MCLog +// +// Created by Alex Lee on 2/25/15. +// Copyright (c) 2015 Yuhua Chen. All rights reserved. +// + +#import "TestViewController.h" + +#define EnableColorLog 1 + +// clang-format off +#define PRETTY_FILE_NAME (__FILE__ ? [[NSString stringWithUTF8String:__FILE__] lastPathComponent] : @"") + +#if DEBUG +# if EnableColorLog +# define __ALLog(LEVEL, fmt, ...) NSLog((@"-" LEVEL @"\e[2;3;4m %s (%@:%d)\e[22;23;24m " fmt), __PRETTY_FUNCTION__, PRETTY_FILE_NAME, __LINE__, ##__VA_ARGS__) +# else +# define __ALLog(LEVEL, fmt, ...) NSLog((@" %s (%@:%d) " fmt), __PRETTY_FUNCTION__, PRETTY_FILE_NAME, __LINE__, ##__VA_ARGS__) +# endif +#else +# define __ALLog(LEVEL, fmt, ...) do {} while (0) +#endif +// clang-format on + +#define ALLogVerbose(fmt, ...) __ALLog(@"[VERBOSE]", fmt, ##__VA_ARGS__) +#define ALLogInfo(fmt, ...) __ALLog(@"[INFO]", fmt, ##__VA_ARGS__) +#define ALLogWarn(fmt, ...) __ALLog(@"[WARN]", fmt, ##__VA_ARGS__) +#define ALLogError(fmt, ...) __ALLog(@"[ERROR]", fmt, ##__VA_ARGS__) + +@interface TestViewController() +@property(nonatomic, weak) UITextField *textField; +@end + +@implementation TestViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UITextField *textField = [[UITextField alloc]initWithFrame:CGRectMake(20, 50, self.view.bounds.size.width - 40, 35)]; + textField.placeholder = @"Input log text case here"; + textField.borderStyle = UITextBorderStyleRoundedRect; + textField.font = [UIFont systemFontOfSize:11.f]; + textField.delegate = self; + [self.view addSubview:textField]; + self.textField = textField; + + CGRect frame = CGRectMake(20, 100, 10, 30); + UIButton *hideKeyboard = [UIButton buttonWithType:UIButtonTypeSystem]; + [hideKeyboard setTitle:@"V" forState:UIControlStateNormal]; + [hideKeyboard setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; + [hideKeyboard addTarget:self action:@selector(hideKeyboardButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + hideKeyboard.frame = frame; + [self.view addSubview:hideKeyboard]; + + frame = CGRectMake(50, 100, 60, 30); + UIButton *verbose = [UIButton buttonWithType:UIButtonTypeSystem]; + [verbose setTitle:@"Verbose" forState:UIControlStateNormal]; + [verbose addTarget:self action:@selector(verboseButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + verbose.frame = frame; + [self.view addSubview:verbose]; + + frame.origin.x += verbose.bounds.size.width + 20; + UIButton *info = [UIButton buttonWithType:UIButtonTypeSystem]; + [info setTitle:@"Info" forState:UIControlStateNormal]; + [info addTarget:self action:@selector(infoButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + info.frame = frame; + [self.view addSubview:info]; + + frame.origin.x += info.bounds.size.width + 20; + UIButton *warn = [UIButton buttonWithType:UIButtonTypeSystem]; + [warn setTitle:@"Warn" forState:UIControlStateNormal]; + [warn addTarget:self action:@selector(warnButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + warn.frame = frame; + [self.view addSubview:warn]; + + frame.origin.x += warn.bounds.size.width + 20; + UIButton *error = [UIButton buttonWithType:UIButtonTypeSystem]; + [error setTitle:@"Error" forState:UIControlStateNormal]; + [error addTarget:self action:@selector(errorButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + error.frame = frame; + [self.view addSubview:error]; + + frame.origin.x = 20; + frame.origin.y += 50; + frame.size.width = self.textField.bounds.size.width; + frame.size.height = self.view.bounds.size.height - frame.origin.y; + UITextView *textView = [[UITextView alloc] initWithFrame:frame]; + textView.font = [UIFont systemFontOfSize:11.f]; + textView.editable = NO; + + NSString *tip = @"----- ANSI Escape Code -----\n\n" + @">>> Text attributes: \n" + @"0 All attributes off \n" + @"1/21 Bold on / off \n" + @"2/22 Faint on / off \n" + @"3/23 Italic on / off\n" + @"4/24 Underline on / off \n" + @"7/27 Image negative / positive \n" + @"8/28 Concealed on / off \n\n" + + @">>> Foreground colors: \n" + @"30 Black \n" + @"31 Red \n" + @"32 Green \n" + @"33 Yellow \n" + @"34 Blue \n" + @"35 Magenta \n" + @"36 Cyan \n" + @"37 White \n" + @"39 Reset foreground color \n\n" + + @">>> Background colors: \n" + @"40 Black \n" + @"41 Red \n" + @"42 Green \n" + @"43 Yellow \n" + @"44 Blue \n" + @"45 Magenta \n" + @"46 Cyan \n" + @"47 White \n" + @"49 Reset background color \n\n"; + textView.attributedText = [[NSAttributedString alloc] initWithString:tip]; + [self.view addSubview:textView]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + NSLog(@"original input: %@", textField.text); + NSString *text = [textField.text stringByReplacingOccurrencesOfString:@"\\033" withString:@"\033"]; + NSLog(@"%@", text); + textField.text = nil; + return YES; +} + +- (void)hideKeyboardButtonClick:(UIButton *)sender { + [self.textField resignFirstResponder]; +} + +- (void)verboseButtonClick:(UIButton *)sender { + [self.textField becomeFirstResponder]; + self.textField.text = @"-\\033[7m[VERBOSE]\\033[27m "; +} + +- (void)infoButtonClick:(UIButton *)sender { + [self.textField becomeFirstResponder]; + self.textField.text = @"-\\033[7m[INFO]\\033[27m "; +} + +- (void)warnButtonClick:(UIButton *)sender { + [self.textField becomeFirstResponder]; + self.textField.text = @"-\\033[7m[WARN]\\033[27m "; +} + +- (void)errorButtonClick:(UIButton *)sender { + [self.textField becomeFirstResponder]; + self.textField.text = @"-\\033[7m[ERROR]\\033[27m "; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self testCaseLotsOfLogs]; + //[self testCaseAttributeText]; + //[self testcaseLogLevel]; + }); +} + +- (void)testCaseAttributeText { + NSLog(@"11111\e[32;2m222222\e[33m333333\e[44m4444444\e[45;22m5555555\e[0m"); + NSLog(@"11111\e[1m222222\e[1m333333\e[21m4444444\e[21m5555555\e[0m"); + NSLog(@"11111\e[3m222222\e[3m333333\e[23m4444444\e[23m5555555\e[0m"); + NSLog(@"11111\e[4m222222\e[4m333333\e[24m4444444\e[24m5555555\e[0m"); + NSLog(@"11111\e[32;2m222222\e[7m333333\e[45m4444444\e[41m88888\e[27m5555555\e[0m"); + NSLog(@"11111\033[32m222222\033[8meeeeeeeeeee\033[33m4444444444\033[28m66666666\033[0m"); + NSLog(@"11111\e[32m222222\e[8meeeeeeeeeee\e[33;7m4444444444\e[28m66666666\e[0m"); +} + +- (void)testcaseLogLevel { + ALLogVerbose(@"This is VERBOSE message"); + ALLogInfo(@"This is INFO message"); + ALLogWarn(@"This is WARN message"); + ALLogError(@"This is ERROR message"); + NSLog(@"-[VERBOSE] verbose message"); + NSLog(@"-[INFO] \033[2;3;4minfo message"); + NSLog(@"-[WARN] warn message"); + NSLog(@"-[ERROR] error message"); +} + + +- (void)testCaseLotsOfLogs { + for (NSUInteger count = 0; count < 10000; ++count) { + if (count % 1000 == 0) { + [NSThread sleepForTimeInterval:0.1]; + } + NSUInteger random = arc4random() % 4; + NSUInteger randomStringLen = arc4random() % 200; + NSMutableString *randomString = [NSMutableString stringWithCapacity:randomStringLen]; + for (NSUInteger i = 0; i < randomStringLen; ++i) { + [randomString appendFormat:@"%02X", arc4random() % 256]; + } + if (random == 0) { + ALLogVerbose(@"***[%tu]***:If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.%@", count, randomString); + } else if (random == 1) { + ALLogInfo(@"***[%tu]***:Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.%@", count, randomString); + } else if (random == 2) { + ALLogWarn(@"***[%tu]***:If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.%@", count, randomString); + } else if (random == 3) { + ALLogError(@"***[%tu]***:Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.%@", count, randomString); + } + } + + //[@"" performSelector:NSSelectorFromString(@"hello_alex")]; +} + +@end diff --git a/MCLogIOS_Demo/main.m b/MCLogIOS_Demo/main.m new file mode 100644 index 0000000..c4dbcef --- /dev/null +++ b/MCLogIOS_Demo/main.m @@ -0,0 +1,16 @@ +// +// main.m +// MCLogIOS_Demo +// +// Created by Alex Lee on 2/25/15. +// Copyright (c) 2015 Yuhua Chen. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/MCLogIOS_DemoTests/Info.plist b/MCLogIOS_DemoTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/MCLogIOS_DemoTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/MCLogIOS_DemoTests/MCLogIOS_DemoTests.m b/MCLogIOS_DemoTests/MCLogIOS_DemoTests.m new file mode 100644 index 0000000..49b7495 --- /dev/null +++ b/MCLogIOS_DemoTests/MCLogIOS_DemoTests.m @@ -0,0 +1,40 @@ +// +// MCLogIOS_DemoTests.m +// MCLogIOS_DemoTests +// +// Created by Alex Lee on 2/25/15. +// Copyright (c) 2015 Yuhua Chen. All rights reserved. +// + +#import +#import + +@interface MCLogIOS_DemoTests : XCTestCase + +@end + +@implementation MCLogIOS_DemoTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample { + // This is an example of a functional test case. + XCTAssert(YES, @"Pass"); +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end