diff --git a/BBUncrustifyPlugin.xcodeproj/project.pbxproj b/BBUncrustifyPlugin.xcodeproj/project.pbxproj index d43388c..be1b191 100755 --- a/BBUncrustifyPlugin.xcodeproj/project.pbxproj +++ b/BBUncrustifyPlugin.xcodeproj/project.pbxproj @@ -7,20 +7,24 @@ objects = { /* Begin PBXBuildFile section */ - 141251A216F4AA8600A18D23 /* BBXcode.m in Sources */ = {isa = PBXBuildFile; fileRef = 141251A116F4AA8600A18D23 /* BBXcode.m */; }; - 1414D3C51710D549002378C0 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1414D3C41710D549002378C0 /* Sparkle.framework */; }; - 1414D3C8171163CA002378C0 /* BBPluginUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 1414D3C7171163CA002378C0 /* BBPluginUpdater.m */; }; - 1414D3CA17116556002378C0 /* sparkle_dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 1414D3C917116556002378C0 /* sparkle_dsa_pub.pem */; }; - 1435669616FD0E0000FECDEB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1435669516FD0E0000FECDEB /* Cocoa.framework */; }; - 1435669816FD0E0400FECDEB /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1435669716FD0E0400FECDEB /* AppKit.framework */; }; - 14617B3316FD9BDB00C7C91B /* DVTKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14617B2F16FD9BDB00C7C91B /* DVTKit.framework */; }; - 14617B3416FD9BDB00C7C91B /* DVTFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14617B3016FD9BDB00C7C91B /* DVTFoundation.framework */; }; - 14617B3516FD9BDB00C7C91B /* IDEFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14617B3116FD9BDB00C7C91B /* IDEFoundation.framework */; }; - 14617B3616FD9BDB00C7C91B /* IDEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14617B3216FD9BDB00C7C91B /* IDEKit.framework */; }; - 147264381711732F002BC0BE /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 147264371711732F002BC0BE /* icon.icns */; }; - 149F2D5C183C073700DFB8CC /* BBUncrustifyPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA37E2DB0E6291C8001BDFEF /* BBUncrustifyPlugin.m */; }; - 149F2D5D183C073700DFB8CC /* BBUncrustify.m in Sources */ = {isa = PBXBuildFile; fileRef = 14FF378C16F490F60049868C /* BBUncrustify.m */; }; - 149F2D5E183C073700DFB8CC /* BBXcode.m in Sources */ = {isa = PBXBuildFile; fileRef = 141251A116F4AA8600A18D23 /* BBXcode.m */; }; + 14626CD618832E5100F5B23F /* XCFPreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 14626CD318832E5100F5B23F /* XCFPreferencesWindowController.m */; }; + 147572B9188BFEED00B3AF8D /* XCFClangFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 147572B8188BFEED00B3AF8D /* XCFClangFormatter.m */; }; + 147572BC188BFF0100B3AF8D /* XCFUncrustifyFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 147572BB188BFF0100B3AF8D /* XCFUncrustifyFormatter.m */; }; + 147572C4188C012900B3AF8D /* XCFPreferencesWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 147572C3188C012900B3AF8D /* XCFPreferencesWindowController.xib */; }; + 147572CB188C01AA00B3AF8D /* CFOFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 147572C6188C01AA00B3AF8D /* CFOFormatter.m */; }; + 147572CC188C01AA00B3AF8D /* CFOClangFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 147572C8188C01AA00B3AF8D /* CFOClangFormatter.m */; }; + 147572CD188C01AA00B3AF8D /* CFOUncrustifyFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 147572CA188C01AA00B3AF8D /* CFOUncrustifyFormatter.m */; }; + 14767CE6188C0E1400080F69 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14767CE5188C0E1400080F69 /* Sparkle.framework */; }; + 14767CE7188C0E1B00080F69 /* Sparkle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 14767CE5188C0E1400080F69 /* Sparkle.framework */; }; + 1486F51D188C06AF003483D5 /* clang-format in Resources */ = {isa = PBXBuildFile; fileRef = 1486F51C188C06AF003483D5 /* clang-format */; }; + 14910D4E188C0D1F005497C9 /* DiffMatchPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14910D42188C0D1F005497C9 /* DiffMatchPatch.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 14910D4F188C0D1F005497C9 /* DiffMatchPatchCFUtilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 14910D43188C0D1F005497C9 /* DiffMatchPatchCFUtilities.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 14910D50188C0D1F005497C9 /* NSMutableDictionary+DMPExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 14910D47188C0D1F005497C9 /* NSMutableDictionary+DMPExtensions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 14910D51188C0D1F005497C9 /* NSString+JavaSubstring.m in Sources */ = {isa = PBXBuildFile; fileRef = 14910D49188C0D1F005497C9 /* NSString+JavaSubstring.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 14910D52188C0D1F005497C9 /* NSString+UnicharUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 14910D4B188C0D1F005497C9 /* NSString+UnicharUtilities.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 14910D53188C0D1F005497C9 /* NSString+UriCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 14910D4D188C0D1F005497C9 /* NSString+UriCompatibility.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 149F2D5C183C073700DFB8CC /* XCFPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA37E2DB0E6291C8001BDFEF /* XCFPlugin.m */; }; + 149F2D5E183C073700DFB8CC /* XCFXcodeFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 141251A116F4AA8600A18D23 /* XCFXcodeFormatter.m */; }; 149F2D5F183C073700DFB8CC /* BBPluginUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 1414D3C7171163CA002378C0 /* BBPluginUpdater.m */; }; 149F2D61183C073700DFB8CC /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1435669716FD0E0400FECDEB /* AppKit.framework */; }; 149F2D62183C073700DFB8CC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1435669516FD0E0000FECDEB /* Cocoa.framework */; }; @@ -29,18 +33,13 @@ 149F2D65183C073700DFB8CC /* DVTFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14617B3016FD9BDB00C7C91B /* DVTFoundation.framework */; }; 149F2D66183C073700DFB8CC /* IDEFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14617B3116FD9BDB00C7C91B /* IDEFoundation.framework */; }; 149F2D67183C073700DFB8CC /* IDEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14617B3216FD9BDB00C7C91B /* IDEKit.framework */; }; - 149F2D68183C073700DFB8CC /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1414D3C41710D549002378C0 /* Sparkle.framework */; }; 149F2D6A183C073700DFB8CC /* uncrustify.cfg in Resources */ = {isa = PBXBuildFile; fileRef = 14CE84DA16F49D6F008082AB /* uncrustify.cfg */; }; 149F2D6B183C073700DFB8CC /* uncrustify in Resources */ = {isa = PBXBuildFile; fileRef = 14CE84D916F49A02008082AB /* uncrustify */; }; 149F2D6C183C073700DFB8CC /* sparkle_dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 1414D3C917116556002378C0 /* sparkle_dsa_pub.pem */; }; 149F2D6D183C073700DFB8CC /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 147264371711732F002BC0BE /* icon.icns */; }; - 149F2D6F183C073700DFB8CC /* Sparkle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 1414D3C41710D549002378C0 /* Sparkle.framework */; }; - 14CE84DC16F4A57A008082AB /* uncrustify.cfg in Resources */ = {isa = PBXBuildFile; fileRef = 14CE84DA16F49D6F008082AB /* uncrustify.cfg */; }; - 14CE84DD16F4A57D008082AB /* uncrustify in Resources */ = {isa = PBXBuildFile; fileRef = 14CE84D916F49A02008082AB /* uncrustify */; }; - 14EFEEDA171167BB0068BB83 /* Sparkle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 1414D3C41710D549002378C0 /* Sparkle.framework */; }; - 14FF378D16F490F60049868C /* BBUncrustify.m in Sources */ = {isa = PBXBuildFile; fileRef = 14FF378C16F490F60049868C /* BBUncrustify.m */; }; - DA1B5D020E64686800921439 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 089C1672FE841209C02AAC07 /* Foundation.framework */; }; - DA37E2DC0E6291C8001BDFEF /* BBUncrustifyPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA37E2DB0E6291C8001BDFEF /* BBUncrustifyPlugin.m */; }; + 14A54E55188AF61900B154C6 /* XCFFormatterUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 14A54E54188AF61900B154C6 /* XCFFormatterUtilities.m */; }; + 14C006C11884936A00488011 /* BBHyperLinkButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C006BF1884936A00488011 /* BBHyperLinkButtonCell.m */; }; + 14D323C21884980B00AB663E /* XCFDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 14D323C01884980500AB663E /* XCFDefaults.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -50,18 +49,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 149F2D6F183C073700DFB8CC /* Sparkle.framework in Copy Frameworks */, - ); - name = "Copy Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - 14EFEED9171167950068BB83 /* Copy Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 14EFEEDA171167BB0068BB83 /* Sparkle.framework in Copy Frameworks */, + 14767CE7188C0E1B00080F69 /* Sparkle.framework in Copy Frameworks */, ); name = "Copy Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -70,8 +58,7 @@ /* Begin PBXFileReference section */ 089C1672FE841209C02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; - 141251A116F4AA8600A18D23 /* BBXcode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BBXcode.m; sourceTree = ""; }; - 1414D3C41710D549002378C0 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; + 141251A116F4AA8600A18D23 /* XCFXcodeFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCFXcodeFormatter.m; sourceTree = ""; }; 1414D3C6171163CA002378C0 /* BBPluginUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BBPluginUpdater.h; sourceTree = ""; }; 1414D3C7171163CA002378C0 /* BBPluginUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BBPluginUpdater.m; sourceTree = ""; }; 1414D3C917116556002378C0 /* sparkle_dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = sparkle_dsa_pub.pem; sourceTree = ""; }; @@ -81,17 +68,50 @@ 14617B3016FD9BDB00C7C91B /* DVTFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DVTFoundation.framework; path = ../../../../../../../Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework; sourceTree = ""; }; 14617B3116FD9BDB00C7C91B /* IDEFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IDEFoundation.framework; path = ../../../../../../../Applications/Xcode.app/Contents/Frameworks/IDEFoundation.framework; sourceTree = ""; }; 14617B3216FD9BDB00C7C91B /* IDEKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IDEKit.framework; path = ../../../../../../../Applications/Xcode.app/Contents/Frameworks/IDEKit.framework; sourceTree = ""; }; + 14626CD218832E5100F5B23F /* XCFPreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFPreferencesWindowController.h; sourceTree = ""; }; + 14626CD318832E5100F5B23F /* XCFPreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCFPreferencesWindowController.m; sourceTree = ""; }; 147264371711732F002BC0BE /* icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon.icns; sourceTree = ""; }; - 149B117816F483D3000F9C01 /* BBXcode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BBXcode.h; sourceTree = ""; }; + 147572B7188BFEED00B3AF8D /* XCFClangFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFClangFormatter.h; sourceTree = ""; }; + 147572B8188BFEED00B3AF8D /* XCFClangFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCFClangFormatter.m; sourceTree = ""; }; + 147572BA188BFF0100B3AF8D /* XCFUncrustifyFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFUncrustifyFormatter.h; sourceTree = ""; }; + 147572BB188BFF0100B3AF8D /* XCFUncrustifyFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCFUncrustifyFormatter.m; sourceTree = ""; }; + 147572C3188C012900B3AF8D /* XCFPreferencesWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = XCFPreferencesWindowController.xib; sourceTree = ""; }; + 147572C5188C01AA00B3AF8D /* CFOFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFOFormatter.h; sourceTree = ""; }; + 147572C6188C01AA00B3AF8D /* CFOFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFOFormatter.m; sourceTree = ""; }; + 147572C7188C01AA00B3AF8D /* CFOClangFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFOClangFormatter.h; sourceTree = ""; }; + 147572C8188C01AA00B3AF8D /* CFOClangFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFOClangFormatter.m; sourceTree = ""; }; + 147572C9188C01AA00B3AF8D /* CFOUncrustifyFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFOUncrustifyFormatter.h; sourceTree = ""; }; + 147572CA188C01AA00B3AF8D /* CFOUncrustifyFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFOUncrustifyFormatter.m; sourceTree = ""; }; + 14767CE5188C0E1400080F69 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Externals/Sparkle.framework; sourceTree = ""; }; + 1486F51C188C06AF003483D5 /* clang-format */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = "clang-format"; sourceTree = ""; }; + 14910D41188C0D1F005497C9 /* DiffMatchPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiffMatchPatch.h; sourceTree = ""; }; + 14910D42188C0D1F005497C9 /* DiffMatchPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiffMatchPatch.m; sourceTree = ""; }; + 14910D43188C0D1F005497C9 /* DiffMatchPatchCFUtilities.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DiffMatchPatchCFUtilities.c; sourceTree = ""; }; + 14910D44188C0D1F005497C9 /* DiffMatchPatchCFUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiffMatchPatchCFUtilities.h; sourceTree = ""; }; + 14910D45188C0D1F005497C9 /* MinMaxMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MinMaxMacros.h; sourceTree = ""; }; + 14910D46188C0D1F005497C9 /* NSMutableDictionary+DMPExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableDictionary+DMPExtensions.h"; sourceTree = ""; }; + 14910D47188C0D1F005497C9 /* NSMutableDictionary+DMPExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableDictionary+DMPExtensions.m"; sourceTree = ""; }; + 14910D48188C0D1F005497C9 /* NSString+JavaSubstring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+JavaSubstring.h"; sourceTree = ""; }; + 14910D49188C0D1F005497C9 /* NSString+JavaSubstring.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+JavaSubstring.m"; sourceTree = ""; }; + 14910D4A188C0D1F005497C9 /* NSString+UnicharUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+UnicharUtilities.h"; sourceTree = ""; }; + 14910D4B188C0D1F005497C9 /* NSString+UnicharUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+UnicharUtilities.m"; sourceTree = ""; }; + 14910D4C188C0D1F005497C9 /* NSString+UriCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+UriCompatibility.h"; sourceTree = ""; }; + 14910D4D188C0D1F005497C9 /* NSString+UriCompatibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+UriCompatibility.m"; sourceTree = ""; }; + 149B117816F483D3000F9C01 /* XCFXcodeFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFXcodeFormatter.h; sourceTree = ""; }; 149F2D72183C073700DFB8CC /* UncrustifyPlugin.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UncrustifyPlugin.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; + 14A54E4B188AE09400B154C6 /* XCFXcodePrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCFXcodePrivate.h; sourceTree = ""; }; + 14A54E53188AF61900B154C6 /* XCFFormatterUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFFormatterUtilities.h; sourceTree = ""; }; + 14A54E54188AF61900B154C6 /* XCFFormatterUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCFFormatterUtilities.m; sourceTree = ""; }; + 14A54E56188AF6C800B154C6 /* XCFConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFConstants.h; sourceTree = ""; }; + 14C006BE1884936A00488011 /* BBHyperLinkButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BBHyperLinkButtonCell.h; sourceTree = ""; }; + 14C006BF1884936A00488011 /* BBHyperLinkButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BBHyperLinkButtonCell.m; sourceTree = ""; }; 14CE84D916F49A02008082AB /* uncrustify */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = uncrustify; sourceTree = ""; }; 14CE84DA16F49D6F008082AB /* uncrustify.cfg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = uncrustify.cfg; sourceTree = ""; }; - 14FF378B16F490F60049868C /* BBUncrustify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BBUncrustify.h; sourceTree = ""; }; - 14FF378C16F490F60049868C /* BBUncrustify.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BBUncrustify.m; sourceTree = ""; }; - 7F2B355F15FA59D000DB3249 /* BBUncrustifyPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BBUncrustifyPlugin.h; sourceTree = ""; }; - 8D5B49B6048680CD000E48DA /* UncrustifyPlugin.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UncrustifyPlugin.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; + 14D323BF1884980500AB663E /* XCFDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFDefaults.h; sourceTree = ""; }; + 14D323C01884980500AB663E /* XCFDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCFDefaults.m; sourceTree = ""; }; + 7F2B355F15FA59D000DB3249 /* XCFPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCFPlugin.h; sourceTree = ""; }; 8D5B49B7048680CD000E48DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DA37E2DB0E6291C8001BDFEF /* BBUncrustifyPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BBUncrustifyPlugin.m; sourceTree = ""; }; + DA37E2DB0E6291C8001BDFEF /* XCFPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCFPlugin.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -106,22 +126,7 @@ 149F2D65183C073700DFB8CC /* DVTFoundation.framework in Frameworks */, 149F2D66183C073700DFB8CC /* IDEFoundation.framework in Frameworks */, 149F2D67183C073700DFB8CC /* IDEKit.framework in Frameworks */, - 149F2D68183C073700DFB8CC /* Sparkle.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8D5B49B3048680CD000E48DA /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1435669816FD0E0400FECDEB /* AppKit.framework in Frameworks */, - 1435669616FD0E0000FECDEB /* Cocoa.framework in Frameworks */, - DA1B5D020E64686800921439 /* Foundation.framework in Frameworks */, - 14617B3316FD9BDB00C7C91B /* DVTKit.framework in Frameworks */, - 14617B3416FD9BDB00C7C91B /* DVTFoundation.framework in Frameworks */, - 14617B3516FD9BDB00C7C91B /* IDEFoundation.framework in Frameworks */, - 14617B3616FD9BDB00C7C91B /* IDEKit.framework in Frameworks */, - 1414D3C51710D549002378C0 /* Sparkle.framework in Frameworks */, + 14767CE6188C0E1400080F69 /* Sparkle.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -132,6 +137,7 @@ isa = PBXGroup; children = ( 7F411B0C15FABAC6002F77B6 /* Classes */, + 14910D3F188C0CD0005497C9 /* Externals */, 089C167CFE841241C02AAC07 /* Resources */, 089C1671FE841209C02AAC07 /* Frameworks and Libraries */, 19C28FB8FE9D52D311CA2CBB /* Products */, @@ -142,7 +148,7 @@ 089C1671FE841209C02AAC07 /* Frameworks and Libraries */ = { isa = PBXGroup; children = ( - 1414D3C41710D549002378C0 /* Sparkle.framework */, + 14767CE5188C0E1400080F69 /* Sparkle.framework */, 14617B2F16FD9BDB00C7C91B /* DVTKit.framework */, 14617B3016FD9BDB00C7C91B /* DVTFoundation.framework */, 14617B3116FD9BDB00C7C91B /* IDEFoundation.framework */, @@ -157,19 +163,112 @@ 089C167CFE841241C02AAC07 /* Resources */ = { isa = PBXGroup; children = ( + 147572C3188C012900B3AF8D /* XCFPreferencesWindowController.xib */, 8D5B49B7048680CD000E48DA /* Info.plist */, 147264371711732F002BC0BE /* icon.icns */, 14CE84DA16F49D6F008082AB /* uncrustify.cfg */, 14CE84D916F49A02008082AB /* uncrustify */, + 1486F51C188C06AF003483D5 /* clang-format */, 1414D3C917116556002378C0 /* sparkle_dsa_pub.pem */, ); path = Resources; sourceTree = ""; }; + 147572B5188BFD2900B3AF8D /* CFOFormatter */ = { + isa = PBXGroup; + children = ( + 147572C5188C01AA00B3AF8D /* CFOFormatter.h */, + 147572C6188C01AA00B3AF8D /* CFOFormatter.m */, + 147572C7188C01AA00B3AF8D /* CFOClangFormatter.h */, + 147572C8188C01AA00B3AF8D /* CFOClangFormatter.m */, + 147572C9188C01AA00B3AF8D /* CFOUncrustifyFormatter.h */, + 147572CA188C01AA00B3AF8D /* CFOUncrustifyFormatter.m */, + ); + name = CFOFormatter; + sourceTree = ""; + }; + 147572BD188BFF2900B3AF8D /* Core */ = { + isa = PBXGroup; + children = ( + 14A54E4B188AE09400B154C6 /* XCFXcodePrivate.h */, + 149B117816F483D3000F9C01 /* XCFXcodeFormatter.h */, + 141251A116F4AA8600A18D23 /* XCFXcodeFormatter.m */, + 14A54E53188AF61900B154C6 /* XCFFormatterUtilities.h */, + 14A54E54188AF61900B154C6 /* XCFFormatterUtilities.m */, + 147572B7188BFEED00B3AF8D /* XCFClangFormatter.h */, + 147572B8188BFEED00B3AF8D /* XCFClangFormatter.m */, + 147572BA188BFF0100B3AF8D /* XCFUncrustifyFormatter.h */, + 147572BB188BFF0100B3AF8D /* XCFUncrustifyFormatter.m */, + ); + name = Core; + sourceTree = ""; + }; + 147572BE188BFF8C00B3AF8D /* Definitions */ = { + isa = PBXGroup; + children = ( + 14A54E56188AF6C800B154C6 /* XCFConstants.h */, + 14D323BF1884980500AB663E /* XCFDefaults.h */, + 14D323C01884980500AB663E /* XCFDefaults.m */, + ); + name = Definitions; + sourceTree = ""; + }; + 147572BF188BFFB300B3AF8D /* Misc */ = { + isa = PBXGroup; + children = ( + 14C006BE1884936A00488011 /* BBHyperLinkButtonCell.h */, + 14C006BF1884936A00488011 /* BBHyperLinkButtonCell.m */, + 1414D3C6171163CA002378C0 /* BBPluginUpdater.h */, + 1414D3C7171163CA002378C0 /* BBPluginUpdater.m */, + ); + name = Misc; + sourceTree = ""; + }; + 147572C0188C004000B3AF8D /* Plugin */ = { + isa = PBXGroup; + children = ( + 147572BF188BFFB300B3AF8D /* Misc */, + 147572BE188BFF8C00B3AF8D /* Definitions */, + 147572BD188BFF2900B3AF8D /* Core */, + 7F2B355F15FA59D000DB3249 /* XCFPlugin.h */, + DA37E2DB0E6291C8001BDFEF /* XCFPlugin.m */, + 14626CD218832E5100F5B23F /* XCFPreferencesWindowController.h */, + 14626CD318832E5100F5B23F /* XCFPreferencesWindowController.m */, + ); + name = Plugin; + sourceTree = ""; + }; + 14910D3F188C0CD0005497C9 /* Externals */ = { + isa = PBXGroup; + children = ( + 14910D40188C0D11005497C9 /* google-diff-match-patch */, + ); + path = Externals; + sourceTree = ""; + }; + 14910D40188C0D11005497C9 /* google-diff-match-patch */ = { + isa = PBXGroup; + children = ( + 14910D41188C0D1F005497C9 /* DiffMatchPatch.h */, + 14910D42188C0D1F005497C9 /* DiffMatchPatch.m */, + 14910D43188C0D1F005497C9 /* DiffMatchPatchCFUtilities.c */, + 14910D44188C0D1F005497C9 /* DiffMatchPatchCFUtilities.h */, + 14910D45188C0D1F005497C9 /* MinMaxMacros.h */, + 14910D46188C0D1F005497C9 /* NSMutableDictionary+DMPExtensions.h */, + 14910D47188C0D1F005497C9 /* NSMutableDictionary+DMPExtensions.m */, + 14910D48188C0D1F005497C9 /* NSString+JavaSubstring.h */, + 14910D49188C0D1F005497C9 /* NSString+JavaSubstring.m */, + 14910D4A188C0D1F005497C9 /* NSString+UnicharUtilities.h */, + 14910D4B188C0D1F005497C9 /* NSString+UnicharUtilities.m */, + 14910D4C188C0D1F005497C9 /* NSString+UriCompatibility.h */, + 14910D4D188C0D1F005497C9 /* NSString+UriCompatibility.m */, + ); + path = "google-diff-match-patch"; + sourceTree = ""; + }; 19C28FB8FE9D52D311CA2CBB /* Products */ = { isa = PBXGroup; children = ( - 8D5B49B6048680CD000E48DA /* UncrustifyPlugin.xcplugin */, 149F2D72183C073700DFB8CC /* UncrustifyPlugin.xcplugin */, ); name = Products; @@ -178,14 +277,8 @@ 7F411B0C15FABAC6002F77B6 /* Classes */ = { isa = PBXGroup; children = ( - 7F2B355F15FA59D000DB3249 /* BBUncrustifyPlugin.h */, - DA37E2DB0E6291C8001BDFEF /* BBUncrustifyPlugin.m */, - 14FF378B16F490F60049868C /* BBUncrustify.h */, - 14FF378C16F490F60049868C /* BBUncrustify.m */, - 149B117816F483D3000F9C01 /* BBXcode.h */, - 141251A116F4AA8600A18D23 /* BBXcode.m */, - 1414D3C6171163CA002378C0 /* BBPluginUpdater.h */, - 1414D3C7171163CA002378C0 /* BBPluginUpdater.m */, + 147572C0188C004000B3AF8D /* Plugin */, + 147572B5188BFD2900B3AF8D /* CFOFormatter */, ); path = Classes; sourceTree = ""; @@ -193,9 +286,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 149F2D5A183C073700DFB8CC /* BBUncrustifyPlugin-ARC */ = { + 149F2D5A183C073700DFB8CC /* BBUncrustifyPlugin */ = { isa = PBXNativeTarget; - buildConfigurationList = 149F2D70183C073700DFB8CC /* Build configuration list for PBXNativeTarget "BBUncrustifyPlugin-ARC" */; + buildConfigurationList = 149F2D70183C073700DFB8CC /* Build configuration list for PBXNativeTarget "BBUncrustifyPlugin" */; buildPhases = ( 149F2D5B183C073700DFB8CC /* Sources */, 149F2D60183C073700DFB8CC /* Frameworks */, @@ -207,32 +300,12 @@ ); dependencies = ( ); - name = "BBUncrustifyPlugin-ARC"; + name = BBUncrustifyPlugin; productInstallPath = "$(HOME)/Library/Bundles"; productName = QuietXcode; productReference = 149F2D72183C073700DFB8CC /* UncrustifyPlugin.xcplugin */; productType = "com.apple.product-type.bundle"; }; - 8D5B49AC048680CD000E48DA /* BBUncrustifyPlugin-GC */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "BBUncrustifyPlugin-GC" */; - buildPhases = ( - 8D5B49B1048680CD000E48DA /* Sources */, - 8D5B49B3048680CD000E48DA /* Frameworks */, - 14CE84DB16F4A573008082AB /* Resources */, - 14EFEED9171167950068BB83 /* Copy Frameworks */, - 1453E770183C0EC500AA9236 /* Code Signing */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "BBUncrustifyPlugin-GC"; - productInstallPath = "$(HOME)/Library/Bundles"; - productName = QuietXcode; - productReference = 8D5B49B6048680CD000E48DA /* UncrustifyPlugin.xcplugin */; - productType = "com.apple.product-type.bundle"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -255,8 +328,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 8D5B49AC048680CD000E48DA /* BBUncrustifyPlugin-GC */, - 149F2D5A183C073700DFB8CC /* BBUncrustifyPlugin-ARC */, + 149F2D5A183C073700DFB8CC /* BBUncrustifyPlugin */, ); }; /* End PBXProject section */ @@ -267,23 +339,14 @@ buildActionMask = 2147483647; files = ( 149F2D6A183C073700DFB8CC /* uncrustify.cfg in Resources */, + 1486F51D188C06AF003483D5 /* clang-format in Resources */, 149F2D6B183C073700DFB8CC /* uncrustify in Resources */, + 147572C4188C012900B3AF8D /* XCFPreferencesWindowController.xib in Resources */, 149F2D6C183C073700DFB8CC /* sparkle_dsa_pub.pem in Resources */, 149F2D6D183C073700DFB8CC /* icon.icns in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 14CE84DB16F4A573008082AB /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 14CE84DC16F4A57A008082AB /* uncrustify.cfg in Resources */, - 14CE84DD16F4A57D008082AB /* uncrustify in Resources */, - 1414D3CA17116556002378C0 /* sparkle_dsa_pub.pem in Resources */, - 147264381711732F002BC0BE /* icon.icns in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -299,21 +362,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"${CODE_SIGN_IDENTITY}\" != \"\" ] &&[ \"${CONFIGURATION}\" = \"Release\" ]; then\necho \"signing identity: ${CODE_SIGN_IDENTITY}\"\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks/\"\nexport CODESIGN_ALLOCATE=\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" Sparkle.framework\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" finish_installation.app\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Resources/\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" uncrustify\nfi"; - }; - 1453E770183C0EC500AA9236 /* Code Signing */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Code Signing"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ \"${CODE_SIGN_IDENTITY}\" != \"\" ] &&[ \"${CONFIGURATION}\" = \"Release\" ]; then\necho \"signing identity: ${CODE_SIGN_IDENTITY}\"\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks/\"\nexport CODESIGN_ALLOCATE=\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" Sparkle.framework\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" finish_installation.app\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Resources/\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" uncrustify\nfi"; + shellScript = "if [ \"${CODE_SIGN_IDENTITY}\" != \"\" ] &&[ \"${CONFIGURATION}\" = \"Release\" ]; then\necho \"signing identity: ${CODE_SIGN_IDENTITY}\"\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks/\"\nexport CODESIGN_ALLOCATE=\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" Sparkle.framework\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" finish_installation.app\ncd \"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Resources/\"\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" uncrustify\ncodesign -f -v -s \"${CODE_SIGN_IDENTITY}\" clang-format\nfi"; }; /* End PBXShellScriptBuildPhase section */ @@ -322,21 +371,24 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 149F2D5C183C073700DFB8CC /* BBUncrustifyPlugin.m in Sources */, - 149F2D5D183C073700DFB8CC /* BBUncrustify.m in Sources */, - 149F2D5E183C073700DFB8CC /* BBXcode.m in Sources */, + 149F2D5C183C073700DFB8CC /* XCFPlugin.m in Sources */, + 14D323C21884980B00AB663E /* XCFDefaults.m in Sources */, + 14910D50188C0D1F005497C9 /* NSMutableDictionary+DMPExtensions.m in Sources */, + 14A54E55188AF61900B154C6 /* XCFFormatterUtilities.m in Sources */, + 147572CB188C01AA00B3AF8D /* CFOFormatter.m in Sources */, + 147572B9188BFEED00B3AF8D /* XCFClangFormatter.m in Sources */, + 14910D4E188C0D1F005497C9 /* DiffMatchPatch.m in Sources */, + 14626CD618832E5100F5B23F /* XCFPreferencesWindowController.m in Sources */, + 149F2D5E183C073700DFB8CC /* XCFXcodeFormatter.m in Sources */, + 14910D51188C0D1F005497C9 /* NSString+JavaSubstring.m in Sources */, + 14910D52188C0D1F005497C9 /* NSString+UnicharUtilities.m in Sources */, 149F2D5F183C073700DFB8CC /* BBPluginUpdater.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8D5B49B1048680CD000E48DA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DA37E2DC0E6291C8001BDFEF /* BBUncrustifyPlugin.m in Sources */, - 14FF378D16F490F60049868C /* BBUncrustify.m in Sources */, - 141251A216F4AA8600A18D23 /* BBXcode.m in Sources */, - 1414D3C8171163CA002378C0 /* BBPluginUpdater.m in Sources */, + 14C006C11884936A00488011 /* BBHyperLinkButtonCell.m in Sources */, + 147572CC188C01AA00B3AF8D /* CFOClangFormatter.m in Sources */, + 14910D4F188C0D1F005497C9 /* DiffMatchPatchCFUtilities.c in Sources */, + 147572BC188BFF0100B3AF8D /* XCFUncrustifyFormatter.m in Sources */, + 147572CD188C01AA00B3AF8D /* CFOUncrustifyFormatter.m in Sources */, + 14910D53188C0D1F005497C9 /* NSString+UriCompatibility.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -374,30 +426,6 @@ }; name = Release; }; - 14125331183D4E0600EB7149 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - COMBINE_HIDPI_IMAGES = YES; - DEPLOYMENT_LOCATION = YES; - DEPLOYMENT_POSTPROCESSING = YES; - DSTROOT = "$(HOME)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/SharedFrameworks\"", - "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Frameworks\"", - "\"$(SRCROOT)\"", - ); - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = Resources/Info.plist; - INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; - LD_RUNPATH_SEARCH_PATHS = /Developer; - OTHER_CFLAGS = "-fobjc-gc"; - PRODUCT_NAME = UncrustifyPlugin; - WRAPPER_EXTENSION = xcplugin; - }; - name = Release; - }; 14125332183D4E0600EB7149 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -411,9 +439,16 @@ "$(inherited)", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/SharedFrameworks\"", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Frameworks\"", - "\"$(SRCROOT)\"", + "$(SRCROOT)", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + "$(PROJECT_DIR)", + "$(PROJECT_DIR)/Externals", ); GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "/Applications/Xcode51-Beta3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include", + ); INFOPLIST_FILE = Resources/Info.plist; INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; LD_RUNPATH_SEARCH_PATHS = /Developer; @@ -437,44 +472,24 @@ "$(inherited)", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/SharedFrameworks\"", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Frameworks\"", - "\"$(SRCROOT)\"", + "$(SRCROOT)", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + "$(PROJECT_DIR)", + "$(PROJECT_DIR)/Externals", ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = Resources/Info.plist; - INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; - LD_RUNPATH_SEARCH_PATHS = /Developer; - MACOSX_DEPLOYMENT_TARGET = 10.8; - OTHER_CFLAGS = ""; - PRODUCT_NAME = UncrustifyPlugin; - WRAPPER_EXTENSION = xcplugin; - }; - name = Debug; - }; - 1DEB913B08733D840010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - COMBINE_HIDPI_IMAGES = YES; - DEPLOYMENT_LOCATION = YES; - DEPLOYMENT_POSTPROCESSING = YES; - DSTROOT = "$(HOME)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/SharedFrameworks\"", - "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Frameworks\"", - "\"$(SRCROOT)\"", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", + HEADER_SEARCH_PATHS = ( "$(inherited)", + "/Applications/Xcode51-Beta3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include", ); INFOPLIST_FILE = Resources/Info.plist; INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; LD_RUNPATH_SEARCH_PATHS = /Developer; - OTHER_CFLAGS = "-fobjc-gc"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + OTHER_CFLAGS = ""; PRODUCT_NAME = UncrustifyPlugin; WRAPPER_EXTENSION = xcplugin; }; @@ -516,7 +531,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 149F2D70183C073700DFB8CC /* Build configuration list for PBXNativeTarget "BBUncrustifyPlugin-ARC" */ = { + 149F2D70183C073700DFB8CC /* Build configuration list for PBXNativeTarget "BBUncrustifyPlugin" */ = { isa = XCConfigurationList; buildConfigurations = ( 149F2D71183C073700DFB8CC /* Debug */, @@ -525,15 +540,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; - 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "BBUncrustifyPlugin-GC" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB913B08733D840010E9CD /* Debug */, - 14125331183D4E0600EB7149 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "BBUncrustifyPlugin" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Classes/BBHyperLinkButtonCell.h b/Classes/BBHyperLinkButtonCell.h new file mode 100644 index 0000000..7467ac0 --- /dev/null +++ b/Classes/BBHyperLinkButtonCell.h @@ -0,0 +1,10 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import + +@interface BBHyperLinkButtonCell : NSButtonCell + +@end diff --git a/Classes/BBHyperLinkButtonCell.m b/Classes/BBHyperLinkButtonCell.m new file mode 100644 index 0000000..8772a3e --- /dev/null +++ b/Classes/BBHyperLinkButtonCell.m @@ -0,0 +1,25 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "BBHyperLinkButtonCell.h" + +@implementation BBHyperLinkButtonCell + +- (NSAttributedString*)attributedTitle { + + NSMutableAttributedString *attributedTitle = [[NSMutableAttributedString alloc] initWithAttributedString:[super attributedTitle]]; + + NSRange range = NSMakeRange(0, [attributedTitle length]); + + [attributedTitle addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:NSSingleUnderlineStyle] range:range]; + + NSColor *textColor = (self.isHighlighted) ? [NSColor colorWithCalibratedRed:0.117 green:0.376 blue:0.998 alpha:1.000] : [NSColor blueColor]; + + [attributedTitle addAttribute:NSForegroundColorAttributeName value:textColor range:range]; + + return [attributedTitle copy]; +} + +@end diff --git a/Classes/BBPluginUpdater.h b/Classes/BBPluginUpdater.h index 09f0a71..95bb674 100644 --- a/Classes/BBPluginUpdater.h +++ b/Classes/BBPluginUpdater.h @@ -1,9 +1,6 @@ // -// BBPluginUpdater.h -// BBUncrustifyPlugin -// -// Created by Benoît on 07/04/13. -// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. // #import diff --git a/Classes/BBPluginUpdater.m b/Classes/BBPluginUpdater.m index f5014da..3d7569e 100644 --- a/Classes/BBPluginUpdater.m +++ b/Classes/BBPluginUpdater.m @@ -1,9 +1,6 @@ // -// BBPluginUpdater.m -// BBUncrustifyPlugin -// -// Created by Benoît on 07/04/13. -// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. // #import "BBPluginUpdater.h" diff --git a/Classes/BBUncrustify.h b/Classes/BBUncrustify.h deleted file mode 100644 index 99151e2..0000000 --- a/Classes/BBUncrustify.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// BBUncrustify.h -// BBUncrustifyPlugin -// -// Created by Benoît on 16/03/13. -// -// - -// Code inspired by https://github.com/ryanmaxwell/UncrustifyX - -#import - -extern NSString * const BBUncrustifyOptionEvictCommentInsertion; -extern NSString * const BBUncrustifyOptionSourceFilename; -extern NSString * const BBUncrustifyOptionSupplementalConfigurationFolders; // NSArray of NSURL (array of urls representing folders) - -@interface BBUncrustify : NSObject - -+ (NSString *)uncrustifyCodeFragment:(NSString *)codeFragment options:(NSDictionary *)options; -+ (NSURL *)uncrustifyXApplicationURL; -+ (NSURL *)resolvedConfigurationFileURLWithAdditionalLookupFolderURLs:(NSArray *)lookupFolderURLs; // returns the config file URL actually used by Uncrustify. -+ (NSURL *)builtInConfigurationFileURL; // returns the default config file URL of the plugin. -+ (NSArray *)userConfigurationFileURLs; // returns suggested custom config file URLs. -@end diff --git a/Classes/BBUncrustify.m b/Classes/BBUncrustify.m deleted file mode 100644 index e14165a..0000000 --- a/Classes/BBUncrustify.m +++ /dev/null @@ -1,222 +0,0 @@ -// -// BBUncrustify.m -// BBUncrustifyPlugin -// -// Created by Benoît on 16/03/13. -// -// - -#import "BBUncrustify.h" -#import - -static NSString * const BBUncrustifyXBundleIdentifier = @"nz.co.xwell.UncrustifyX"; - -NSString * const BBUncrustifyOptionEvictCommentInsertion = @"evictCommentInsertion"; -NSString * const BBUncrustifyOptionSourceFilename = @"sourceFilename"; -NSString * const BBUncrustifyOptionSupplementalConfigurationFolders = @"supplementalConfigurationFolders"; - -static NSString * BBUUIDString() { -#if __has_feature(objc_arc) - return [[NSUUID UUID] UUIDString]; // ARC is used for Xcode 5.1+, we can use NSUUID available on OS X 10.8+ -#else - NSString *uuidString = nil; - CFUUIDRef uuid = CFUUIDCreate(NULL); - if (uuid) { - uuidString = (NSString *)CFUUIDCreateString(NULL, uuid); - CFRelease(uuid); - } - return [NSMakeCollectable(uuidString) autorelease]; -#endif -} - -@interface BBUncrustify () - -@end - -@implementation BBUncrustify - -+ (NSString *)uncrustifyCodeFragment:(NSString *)codeFragment options:(NSDictionary *)options { - if (!codeFragment) return nil; - - NSString *sourceFileName = options[BBUncrustifyOptionSourceFilename]; - if (!sourceFileName || sourceFileName.length == 0) { - sourceFileName = @"source"; - } - - NSURL *codeFragmentFileURL = [[[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] URLByAppendingPathComponent:BBUUIDString() isDirectory:YES] URLByAppendingPathComponent:sourceFileName isDirectory:NO]; - [[NSFileManager defaultManager] createDirectoryAtPath:[codeFragmentFileURL URLByDeletingLastPathComponent].path withIntermediateDirectories:YES attributes:nil error:nil]; - - [codeFragment writeToURL:codeFragmentFileURL atomically:YES encoding:NSUTF8StringEncoding error:nil]; - - NSArray *additionalLookupFolderURLs = options[BBUncrustifyOptionSupplementalConfigurationFolders]; - if (additionalLookupFolderURLs.count > 0) { - NSLog(@"uncrustify additional lookup folders: %@", additionalLookupFolderURLs); - } - NSURL *configurationFileURL = [BBUncrustify resolvedConfigurationFileURLWithAdditionalLookupFolderURLs:additionalLookupFolderURLs]; - - NSLog(@"uncrustify configuration file: %@", configurationFileURL); - - if ([options[BBUncrustifyOptionEvictCommentInsertion] boolValue]) { - NSString *configuration = [[NSString alloc] initWithContentsOfURL:configurationFileURL encoding:NSUTF8StringEncoding error:nil]; - BOOL hasChanged = NO; - NSString *modifiedConfiguration = [BBUncrustify configurationByRemovingOptions:@[@"cmt_insert_file_"] fromConfiguration:configuration hasChanged:&hasChanged]; -#if !__has_feature(objc_arc) - [configuration release]; -#endif - if (hasChanged) { - configurationFileURL = [[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.cfg", BBUUIDString()] isDirectory:NO]; - [modifiedConfiguration writeToURL:configurationFileURL atomically:YES encoding:NSUTF8StringEncoding error:nil]; - } - } - - [self uncrustifyFilesAtURLs:@[codeFragmentFileURL] configurationFileURL:configurationFileURL]; - - NSError *error = nil; - NSString *result = [NSString stringWithContentsOfURL:codeFragmentFileURL encoding:NSUTF8StringEncoding error:&error]; - - if (error) { - NSLog(@"%@", error); - return nil; - } - - return result; -} - -+ (NSString *)configurationByRemovingOptions:(NSArray *)options fromConfiguration:(NSString *)originalConfiguration hasChanged:(BOOL *)outHasChanged { - __block BOOL hasChanged = NO; - - NSMutableString *mString = [NSMutableString string]; - - [originalConfiguration enumerateSubstringsInRange:NSMakeRange(0, originalConfiguration.length) options:NSStringEnumerationByLines usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - NSString *line = [[substring stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] lowercaseString]; - BOOL optionFound = NO; - for (NSString * option in options) { - NSRange range = [line rangeOfString:option options:NSCaseInsensitiveSearch | NSAnchoredSearch]; - optionFound = (range.location != NSNotFound); - if (optionFound) { - hasChanged = YES; - break; - } - } - if (!optionFound) { - [mString appendString:substring]; - [mString appendString:@"\n"]; - } - }]; - - if (outHasChanged != NULL) { - *outHasChanged = hasChanged; - } - - return [NSString stringWithString:mString]; -} - -+ (NSURL *)builtInConfigurationFileURL { - NSBundle *bundle = [NSBundle bundleForClass:[self class]]; - return [bundle URLForResource:@"uncrustify" withExtension:@"cfg"]; -} - -+ (NSArray *)makeConfigurationFileURLsFromFolderURLs:(NSArray *)folderURLs { - NSMutableArray *mArray = [NSMutableArray array]; - for (NSURL *folderURL in folderURLs) { - [mArray addObject:[folderURL URLByAppendingPathComponent:@".uncrustifyconfig" isDirectory:NO]]; - [mArray addObject:[folderURL URLByAppendingPathComponent:@"uncrustify.cfg" isDirectory:NO]]; - [mArray addObject:[[folderURL URLByAppendingPathComponent:@".uncrustify" isDirectory:YES] URLByAppendingPathComponent:@"uncrustify.cfg" isDirectory:NO]]; - } - return [NSArray arrayWithArray:mArray]; -} - -+ (NSArray *)userConfigurationFileURLs { - NSURL *homeDirectoryURL = [NSURL fileURLWithPath:NSHomeDirectory()]; - return [BBUncrustify makeConfigurationFileURLsFromFolderURLs:@[homeDirectoryURL]]; -} - -+ (NSURL *)resolvedConfigurationFileURLWithAdditionalLookupFolderURLs:(NSArray *)lookupFolderURLs { // additionalLocations can be nil (optional parameter) - // folders are ordered by priority - NSMutableArray *configurationURLs = [NSMutableArray array]; - if (lookupFolderURLs) { - [configurationURLs addObjectsFromArray:[BBUncrustify makeConfigurationFileURLsFromFolderURLs:lookupFolderURLs]]; - } - [configurationURLs addObjectsFromArray:[BBUncrustify userConfigurationFileURLs]]; - - for (NSURL *url in configurationURLs) { - if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) { - return url; - } - } - return [BBUncrustify builtInConfigurationFileURL]; -} - -+ (NSURL *)builtInExecutableFileURL { - NSBundle *bundle = [NSBundle bundleForClass:[self class]]; - return [bundle URLForResource:@"uncrustify" withExtension:@""]; -} - -+ (NSArray *)userExecutableFileURLs { - NSMutableArray *mArray = [NSMutableArray array]; - [mArray addObject:[NSURL fileURLWithPath:@"/usr/local/bin/uncrustify"]]; - [mArray addObject:[NSURL fileURLWithPath:@"/usr/bin/uncrustify"]]; - return mArray; -} - -+ (NSURL *)resolvedExecutableFileURL { - // folders are ordered by priority - for (NSURL *url in [self userExecutableFileURLs]) { - if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) { - return url; - } - } - return [BBUncrustify builtInExecutableFileURL]; -} - -+ (void)uncrustifyFilesAtURLs:(NSArray *)fileURLs configurationFileURL:(NSURL *)configurationFileURL { - //NSLog(@"uncrustify configuration file: %@",configurationFileURL); - - NSURL *executableFileURL = [self resolvedExecutableFileURL]; - - BOOL filesExists = [[NSFileManager defaultManager] fileExistsAtPath:configurationFileURL.path] && [[NSFileManager defaultManager] fileExistsAtPath:configurationFileURL.path]; - - if (!filesExists) { - return; - } - - [fileURLs enumerateObjectsWithOptions:0 usingBlock:^(NSURL *fileURL, NSUInteger idx, BOOL *stop) { - if ([[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) { - NSMutableArray *args = NSMutableArray.array; - - NSString *uti = [[NSWorkspace sharedWorkspace] typeOfFile:fileURL.path error:nil]; - BOOL isObjectiveCFile = ([[NSWorkspace sharedWorkspace] type:uti conformsToType:(NSString *)kUTTypeObjectiveCSource] || [[NSWorkspace sharedWorkspace] type:uti conformsToType:(NSString *)kUTTypeCHeader]); - if (isObjectiveCFile) { - [args addObjectsFromArray:@[@"-l", @"OC"]]; - } - - [args addObjectsFromArray:@[@"--frag", @"--no-backup"]]; - [args addObjectsFromArray:@[@"-c", configurationFileURL.path, fileURL.path]]; - - NSPipe *outputPipe = NSPipe.pipe; - NSPipe *errorPipe = NSPipe.pipe; - - NSTask *task = [[NSTask alloc] init]; - task.launchPath = executableFileURL.path; - task.arguments = args; - - task.standardOutput = outputPipe; - task.standardError = errorPipe; - - [outputPipe.fileHandleForReading readToEndOfFileInBackgroundAndNotify]; - [errorPipe.fileHandleForReading readToEndOfFileInBackgroundAndNotify]; - - [task launch]; - [task waitUntilExit]; -#if !__has_feature(objc_arc) - [task release]; -#endif - } - }]; -} - -+ (NSURL *)uncrustifyXApplicationURL { - return [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:BBUncrustifyXBundleIdentifier]; -} - -@end diff --git a/Classes/BBUncrustifyPlugin.h b/Classes/BBUncrustifyPlugin.h deleted file mode 100755 index 0f00c21..0000000 --- a/Classes/BBUncrustifyPlugin.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// BBUncrustifyPlugin.h -// BBUncrustifyPlugin -// -// Created by Benoît on 16/03/13. -// -// - -#import -#import - -@interface BBUncrustifyPlugin : NSObject - -@end diff --git a/Classes/BBUncrustifyPlugin.m b/Classes/BBUncrustifyPlugin.m deleted file mode 100755 index 057bcd8..0000000 --- a/Classes/BBUncrustifyPlugin.m +++ /dev/null @@ -1,209 +0,0 @@ -// -// BBUncrustifyPlugin.m -// BBUncrustifyPlugin -// -// Created by Benoît on 16/03/13. -// -// - -#import "BBUncrustifyPlugin.h" -#import "BBUncrustify.h" -#import "BBXcode.h" -#import "BBPluginUpdater.h" - -@implementation BBUncrustifyPlugin {} - -#pragma mark - Setup and Teardown - -static BBUncrustifyPlugin *sharedPlugin = nil; - -+ (void)pluginDidLoad:(NSBundle *)plugin { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedPlugin = [[self alloc] init]; - }); -} - -- (id)init { - self = [super init]; - if (self) { - NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"]; - if (editMenuItem) { - [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]]; - - NSMenuItem *menuItem; - menuItem = [[NSMenuItem alloc] initWithTitle:@"Uncrustify Selected Files" action:@selector(uncrustifySelectedFiles:) keyEquivalent:@""]; - [menuItem setTarget:self]; - [[editMenuItem submenu] addItem:menuItem]; -#if !__has_feature(objc_arc) - [menuItem release]; -#endif - - menuItem = [[NSMenuItem alloc] initWithTitle:@"Uncrustify Active File" action:@selector(uncrustifyActiveFile:) keyEquivalent:@""]; - [menuItem setTarget:self]; - [[editMenuItem submenu] addItem:menuItem]; -#if !__has_feature(objc_arc) - [menuItem release]; -#endif - menuItem = [[NSMenuItem alloc] initWithTitle:@"Uncrustify Selected Lines" action:@selector(uncrustifySelectedLines:) keyEquivalent:@""]; - [menuItem setTarget:self]; - [[editMenuItem submenu] addItem:menuItem]; -#if !__has_feature(objc_arc) - [menuItem release]; -#endif - menuItem = [[NSMenuItem alloc] initWithTitle:@"Open with UncrustifyX" action:@selector(openWithUncrustifyX:) keyEquivalent:@""]; - [menuItem setTarget:self]; - [[editMenuItem submenu] addItem:menuItem]; -#if !__has_feature(objc_arc) - [menuItem release]; -#endif - [BBPluginUpdater sharedUpdater].delegate = self; - - NSLog(@"BBUncrustifyPlugin (V%@) loaded", [[[NSBundle bundleForClass:[self class]] infoDictionary] objectForKey:@"CFBundleVersion"]); - } - } - return self; -} - -#pragma mark - Actions - -- (IBAction)uncrustifySelectedFiles:(id)sender { - NSArray *fileNavigableItems = [BBXcode selectedSourceCodeFileNavigableItems]; - IDEWorkspace *currentWorkspace = [BBXcode currentWorkspaceDocument].workspace; - for (IDEFileNavigableItem *fileNavigableItem in fileNavigableItems) { - NSDocument *document = [IDEDocumentController retainedEditorDocumentForNavigableItem:fileNavigableItem error:nil]; - if ([document isKindOfClass:NSClassFromString(@"IDESourceCodeDocument")]) { - IDESourceCodeDocument *sourceCodeDocument = (IDESourceCodeDocument *)document; - BOOL uncrustified = [BBXcode uncrustifyCodeOfDocument:sourceCodeDocument inWorkspace:currentWorkspace]; - if (uncrustified) { - [document saveDocument:nil]; - } - } - [IDEDocumentController releaseEditorDocument:document]; - } - - [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; -} - -- (IBAction)uncrustifyActiveFile:(id)sender { - IDESourceCodeDocument *document = [BBXcode currentSourceCodeDocument]; - if (!document) return; - - NSTextView *textView = [BBXcode currentSourceCodeTextView]; - - DVTSourceTextStorage *textStorage = [document textStorage]; - - // We try to restore the original cursor position after the uncrustification. We compute a percentage value - // expressing the actual selected line compared to the total number of lines of the document. After the uncrustification, - // we restore the position taking into account the modified number of lines of the document. - - NSRange originalCharacterRange = [textView selectedRange]; - NSRange originalLineRange = [textStorage lineRangeForCharacterRange:originalCharacterRange]; - NSRange originalDocumentLineRange = [textStorage lineRangeForCharacterRange:NSMakeRange(0, textStorage.string.length)]; - - CGFloat verticalRelativePosition = (CGFloat)originalLineRange.location / (CGFloat)originalDocumentLineRange.length; - - IDEWorkspace *currentWorkspace = [BBXcode currentWorkspaceDocument].workspace; - [BBXcode uncrustifyCodeOfDocument:document inWorkspace:currentWorkspace]; - - NSRange newDocumentLineRange = [textStorage lineRangeForCharacterRange:NSMakeRange(0, textStorage.string.length)]; - NSUInteger restoredLine = roundf(verticalRelativePosition * (CGFloat)newDocumentLineRange.length); - - NSRange newCharacterRange = [textStorage characterRangeForLineRange:NSMakeRange(restoredLine, 0)]; - - if (newCharacterRange.location < textStorage.string.length) { - [[BBXcode currentSourceCodeTextView] setSelectedRanges:@[[NSValue valueWithRange:newCharacterRange]]]; - [textView scrollRangeToVisible:newCharacterRange]; - } - - [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; -} - -- (IBAction)uncrustifySelectedLines:(id)sender { - IDESourceCodeDocument *document = [BBXcode currentSourceCodeDocument]; - NSTextView *textView = [BBXcode currentSourceCodeTextView]; - if (!document || !textView) return; - IDEWorkspace *currentWorkspace = [BBXcode currentWorkspaceDocument].workspace; - NSArray *selectedRanges = [textView selectedRanges]; - [BBXcode uncrustifyCodeAtRanges:selectedRanges document:document inWorkspace:currentWorkspace]; - - [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; -} - -- (IBAction)openWithUncrustifyX:(id)sender { - NSURL *appURL = [BBUncrustify uncrustifyXApplicationURL]; - - NSArray *selectedNavigableItems = [BBXcode selectedNavigableItems]; - IDENavigableItem *navigableItem = (selectedNavigableItems.count > 0) ? selectedNavigableItems[0] : nil; - - NSArray *lookupFolderURLs = nil; - - if (navigableItem) { - lookupFolderURLs = [BBXcode containerFolderURLsAncestorsToNavigableItem:navigableItem]; - } - else { - IDEWorkspace *currentWorkspace = [BBXcode currentWorkspaceDocument].workspace; - lookupFolderURLs = @[[currentWorkspace.representingFilePath.fileURL URLByDeletingLastPathComponent]]; - } - - NSURL *configurationFileURL = [BBUncrustify resolvedConfigurationFileURLWithAdditionalLookupFolderURLs:lookupFolderURLs]; - NSURL *builtInConfigurationFileURL = [BBUncrustify builtInConfigurationFileURL]; - if ([configurationFileURL isEqual:builtInConfigurationFileURL]) { - configurationFileURL = [BBUncrustify userConfigurationFileURLs][0]; - NSAlert *alert = [NSAlert alertWithMessageText:@"Custom Configuration File Not Found" defaultButton:@"Create Configuration File & Open UncrustifyX" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Do you want to create a configuration file at this path \n%@", configurationFileURL.path]; - if ([alert runModal] == NSAlertDefaultReturn) { - [[NSFileManager defaultManager] copyItemAtPath:builtInConfigurationFileURL.path toPath:configurationFileURL.path error:nil]; - } else { - configurationFileURL = nil; - } - } - - if (configurationFileURL) { - IDESourceCodeDocument *document = [BBXcode currentSourceCodeDocument]; - if (document) { - DVTSourceTextStorage *textStorage = [document textStorage]; - [[NSPasteboard pasteboardWithName:@"BBUncrustifyPlugin-source-code"] clearContents]; - if (textStorage.string) { - [[NSPasteboard pasteboardWithName:@"BBUncrustifyPlugin-source-code"] writeObjects:@[textStorage.string]]; - } - } - NSDictionary *configuration = @{ NSWorkspaceLaunchConfigurationArguments: @[@"-bbuncrustifyplugin", @"-configpath", configurationFileURL.path] }; - [[NSWorkspace sharedWorkspace]launchApplicationAtURL:appURL options:0 configuration:configuration error:nil]; - } - - [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; -} - -#pragma mark - NSMenuValidation - -- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - if ([menuItem action] == @selector(uncrustifySelectedFiles:)) { - return ([BBXcode selectedSourceCodeFileNavigableItems].count > 0); - } else if ([menuItem action] == @selector(uncrustifyActiveFile:)) { - IDESourceCodeDocument *document = [BBXcode currentSourceCodeDocument]; - return (document != nil); - } else if ([menuItem action] == @selector(uncrustifySelectedLines:)) { - BOOL validated = NO; - IDESourceCodeDocument *document = [BBXcode currentSourceCodeDocument]; - NSTextView *textView = [BBXcode currentSourceCodeTextView]; - if (document && textView) { - NSArray *selectedRanges = [textView selectedRanges]; - validated = (selectedRanges.count > 0); - } - return validated; - } else if ([menuItem action] == @selector(openWithUncrustifyX:)) { - BOOL appExists = NO; - NSURL *appURL = [BBUncrustify uncrustifyXApplicationURL]; - if (appURL) appExists = [[NSFileManager defaultManager] fileExistsAtPath:appURL.path]; - [menuItem setHidden:!appExists]; - } - return YES; -} - -#pragma mark - SUUpdater Delegate - -- (NSString *)pathToRelaunchForUpdater:(SUUpdater *)updater { - return [[NSBundle mainBundle].bundleURL path]; -} - -@end diff --git a/Classes/BBXcode.m b/Classes/BBXcode.m deleted file mode 100644 index 0f44bcf..0000000 --- a/Classes/BBXcode.m +++ /dev/null @@ -1,386 +0,0 @@ -// -// BBXcode.m -// BBUncrustifyPlugin -// -// Created by Benoît on 16/03/13. -// -// - -#import "BBXcode.h" -#import "BBUncrustify.h" - -NSString * const BBUserDefaultsCodeFormattingScheme = @"uncrustify_plugin_codeFormattingScheme"; - -NSArray * BBMergeContinuousRanges(NSArray *ranges) { - if (ranges.count == 0) return nil; - - NSMutableIndexSet *mIndexes = [NSMutableIndexSet indexSet]; - for (NSValue *rangeValue in ranges) { - NSRange range = [rangeValue rangeValue]; - [mIndexes addIndexesInRange:range]; - } - - NSMutableArray *mergedRanges = [NSMutableArray array]; - [mIndexes enumerateRangesUsingBlock:^(NSRange range, BOOL *stop) { - [mergedRanges addObject:[NSValue valueWithRange:range]]; - }]; - return [NSArray arrayWithArray:mergedRanges]; -} - -NSString * BBStringByTrimmingTrailingCharactersFromString(NSString *string, NSCharacterSet *characterSet) { - NSRange rangeOfLastWantedCharacter = [string rangeOfCharacterFromSet:[characterSet invertedSet] options:NSBackwardsSearch]; - if (rangeOfLastWantedCharacter.location == NSNotFound) return @""; - return [string substringToIndex:rangeOfLastWantedCharacter.location + 1]; -} - -@implementation BBXcode {} - -#pragma mark - Helpers - -+ (id)currentEditor { - NSWindowController *currentWindowController = [[NSApp keyWindow] windowController]; - if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { - IDEWorkspaceWindowController *workspaceController = (IDEWorkspaceWindowController *)currentWindowController; - IDEEditorArea *editorArea = [workspaceController editorArea]; - IDEEditorContext *editorContext = [editorArea lastActiveEditorContext]; - return [editorContext editor]; - } - return nil; -} - -+ (IDEWorkspaceDocument *)currentWorkspaceDocument { - NSWindowController *currentWindowController = [[NSApp keyWindow] windowController]; - id document = [currentWindowController document]; - if (currentWindowController && [document isKindOfClass:NSClassFromString(@"IDEWorkspaceDocument")]) { - return (IDEWorkspaceDocument *)document; - } - return nil; -} - -+ (IDESourceCodeDocument *)currentSourceCodeDocument { - if ([[BBXcode currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) { - IDESourceCodeEditor *editor = [BBXcode currentEditor]; - return editor.sourceCodeDocument; - } - - if ([[BBXcode currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) { - IDESourceCodeComparisonEditor *editor = [BBXcode currentEditor]; - if ([[editor primaryDocument] isKindOfClass:NSClassFromString(@"IDESourceCodeDocument")]) { - IDESourceCodeDocument *document = (IDESourceCodeDocument *)editor.primaryDocument; - return document; - } - } - - return nil; -} - -+ (NSTextView *)currentSourceCodeTextView { - if ([[BBXcode currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) { - IDESourceCodeEditor *editor = [BBXcode currentEditor]; - return editor.textView; - } - - if ([[BBXcode currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) { - IDESourceCodeComparisonEditor *editor = [BBXcode currentEditor]; - return editor.keyTextView; - } - - return nil; -} - -+ (NSArray *)selectedNavigableItems { - NSMutableArray *mutableArray = [NSMutableArray array]; - id currentWindowController = [[NSApp keyWindow] windowController]; - if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { - IDEWorkspaceWindowController *workspaceController = currentWindowController; - IDEWorkspaceTabController *workspaceTabController = [workspaceController activeWorkspaceTabController]; - IDENavigatorArea *navigatorArea = [workspaceTabController navigatorArea]; - id currentNavigator = [navigatorArea currentNavigator]; - - if ([currentNavigator isKindOfClass:NSClassFromString(@"IDEStructureNavigator")]) { - IDEStructureNavigator *structureNavigator = currentNavigator; - for (id selectedObject in structureNavigator.selectedObjects) { - if ([selectedObject isKindOfClass:NSClassFromString(@"IDENavigableItem")]) { - [mutableArray addObject:selectedObject]; - } - } - } - } - - if (mutableArray.count) { - return [NSArray arrayWithArray:mutableArray]; - } - return nil; -} - -+ (NSArray *)selectedSourceCodeFileNavigableItems { - NSMutableArray *mutableArray = [NSMutableArray array]; - id currentWindowController = [[NSApp keyWindow] windowController]; - if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { - IDEWorkspaceWindowController *workspaceController = currentWindowController; - IDEWorkspaceTabController *workspaceTabController = [workspaceController activeWorkspaceTabController]; - IDENavigatorArea *navigatorArea = [workspaceTabController navigatorArea]; - id currentNavigator = [navigatorArea currentNavigator]; - - if ([currentNavigator isKindOfClass:NSClassFromString(@"IDEStructureNavigator")]) { - IDEStructureNavigator *structureNavigator = currentNavigator; - for (id selectedObject in structureNavigator.selectedObjects) { - NSArray *arrayOfFiles = [self recursivlyCollectFileNavigableItemsFrom:selectedObject]; - if (arrayOfFiles.count) { - [mutableArray addObjectsFromArray:arrayOfFiles]; - } - } - } - } - - if (mutableArray.count) { - return [NSArray arrayWithArray:mutableArray]; - } - return nil; -} - -+ (NSArray *)recursivlyCollectFileNavigableItemsFrom:(IDENavigableItem *)selectedObject { - id items = nil; - - if ([selectedObject isKindOfClass:NSClassFromString(@"IDEGroupNavigableItem")]) { - //|| [selectedObject isKindOfClass:NSClassFromString(@"IDEContainerFileReferenceNavigableItem")]) { //disallow project - NSMutableArray *mItems = [NSMutableArray array]; - IDEGroupNavigableItem *groupNavigableItem = (IDEGroupNavigableItem *)selectedObject; - for (IDENavigableItem *child in groupNavigableItem.childItems) { - NSArray *childItems = [self recursivlyCollectFileNavigableItemsFrom:child]; - if (childItems.count) { - [mItems addObjectsFromArray:childItems]; - } - } - items = mItems; - } - else if ([selectedObject isKindOfClass:NSClassFromString(@"IDEFileNavigableItem")]) { - IDEFileNavigableItem *fileNavigableItem = (IDEFileNavigableItem *)selectedObject; - NSString *uti = fileNavigableItem.documentType.identifier; - if ([[NSWorkspace sharedWorkspace] type:uti conformsToType:(NSString *)kUTTypeSourceCode]) { - items = @[fileNavigableItem]; - } - } - - return items; -} - -+ (NSArray *)containerFolderURLsForNavigableItem:(IDENavigableItem *)navigableItem { - NSMutableArray *mArray = [NSMutableArray array]; - - do { - NSURL *folderURL = nil; - id representedObject = navigableItem.representedObject; - if ([navigableItem isKindOfClass:NSClassFromString(@"IDEGroupNavigableItem")]) { - // IDE-GROUP (a folder in the navigator) - IDEGroup *group = (IDEGroup *)representedObject; - folderURL = group.resolvedFilePath.fileURL; - } else if ([navigableItem isKindOfClass:NSClassFromString(@"IDEContainerFileReferenceNavigableItem")]) { - // CONTAINER (an Xcode project) - IDEFileReference *fileReference = representedObject; - folderURL = [fileReference.resolvedFilePath.fileURL URLByDeletingLastPathComponent]; - } else if ([navigableItem isKindOfClass:NSClassFromString(@"IDEKeyDrivenNavigableItem")]) { - // WORKSPACE (root: Xcode project or workspace) - IDEWorkspace *workspace = representedObject; - folderURL = [workspace.representingFilePath.fileURL URLByDeletingLastPathComponent]; - } - if (folderURL && ![mArray containsObject:folderURL]) [mArray addObject:folderURL]; - navigableItem = [navigableItem parentItem]; - } while (navigableItem != nil); - - if (mArray.count > 0) return [NSArray arrayWithArray:mArray]; - return nil; -} - -+ (NSArray *)containerFolderURLsAncestorsToNavigableItem:(IDENavigableItem *)navigableItem { - if (navigableItem) { - return [BBXcode containerFolderURLsForNavigableItem:navigableItem]; - } - return nil; -} - -#pragma mark - Uncrustify - -+ (BOOL)uncrustifyCodeOfDocument:(IDESourceCodeDocument *)document inWorkspace:(IDEWorkspace *)workspace { - DVTSourceTextStorage *textStorage = [document textStorage]; - - NSString *originalString = [NSString stringWithString:textStorage.string]; - - if (textStorage.string.length > 0) { - NSArray *additionalConfigurationFolderURLs = nil; - if (workspace) { - IDENavigableItemCoordinator *coordinator = [[IDENavigableItemCoordinator alloc] init]; - IDENavigableItem *navigableItem = [coordinator structureNavigableItemForDocumentURL:document.fileURL inWorkspace:workspace error:nil]; -#if !__has_feature(objc_arc) - [coordinator release]; -#endif - if (navigableItem) { - additionalConfigurationFolderURLs = [BBXcode containerFolderURLsForNavigableItem:navigableItem]; - } - } - - NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:@{ BBUncrustifyOptionSourceFilename: document.fileURL.lastPathComponent }]; - if (additionalConfigurationFolderURLs.count > 0) { - [options setObject:additionalConfigurationFolderURLs forKey:BBUncrustifyOptionSupplementalConfigurationFolders]; - } - - [textStorage beginEditing]; - NSString *uncrustifiedCode = [BBUncrustify uncrustifyCodeFragment:textStorage.string options:options]; - if (![uncrustifiedCode isEqualToString:textStorage.string]) { - [textStorage replaceCharactersInRange:NSMakeRange(0, textStorage.string.length) withString:uncrustifiedCode withUndoManager:[document undoManager]]; - } - [BBXcode normalizeCodeAtRange:NSMakeRange(0, textStorage.string.length) document:document]; - [textStorage endEditing]; - } - - BOOL codeHasChanged = (originalString && ![originalString isEqualToString:textStorage.string]); - return codeHasChanged; -} - -+ (BOOL)uncrustifyCodeAtRanges:(NSArray *)ranges document:(IDESourceCodeDocument *)document inWorkspace:(IDEWorkspace *)workspace { - DVTSourceTextStorage *textStorage = [document textStorage]; - - NSArray *linesRangeValues = nil; - { - NSMutableArray *mLinesRangeValues = [NSMutableArray array]; - for (NSValue *rangeValue in ranges) { - NSRange range = [rangeValue rangeValue]; - NSRange lineRange = [textStorage lineRangeForCharacterRange:range]; - [mLinesRangeValues addObject:[NSValue valueWithRange:lineRange]]; - } - linesRangeValues = BBMergeContinuousRanges(mLinesRangeValues); - } - - NSMutableArray *textFragments = [NSMutableArray array]; - - NSArray *additionalConfigurationFolderURLs = nil; - if (workspace) { - IDENavigableItemCoordinator *coordinator = [[IDENavigableItemCoordinator alloc] init]; - IDENavigableItem *navigableItem = [coordinator structureNavigableItemForDocumentURL:document.fileURL inWorkspace:workspace error:nil]; -#if !__has_feature(objc_arc) - [coordinator release]; -#endif - if (navigableItem) { - additionalConfigurationFolderURLs = [BBXcode containerFolderURLsForNavigableItem:navigableItem]; - } - } - - for (NSValue *linesRangeValue in linesRangeValues) { - NSRange linesRange = [linesRangeValue rangeValue]; - NSRange characterRange = [textStorage characterRangeForLineRange:linesRange]; - if (characterRange.location != NSNotFound) { - NSString *string = [textStorage.string substringWithRange:characterRange]; - if (string.length > 0) { - NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:@{ BBUncrustifyOptionEvictCommentInsertion: @YES, BBUncrustifyOptionSourceFilename: document.fileURL.lastPathComponent }]; - if (additionalConfigurationFolderURLs.count > 0) { - [options setObject:additionalConfigurationFolderURLs forKey:BBUncrustifyOptionSupplementalConfigurationFolders]; - } - - NSString *uncrustifiedString = [BBUncrustify uncrustifyCodeFragment:string options:options]; - if (uncrustifiedString.length > 0) { - [textFragments addObject:@{ @"textFragment": uncrustifiedString, @"range": [NSValue valueWithRange:characterRange] }]; - } - } - } - } - - NSString *originalString = [NSString stringWithString:textStorage.string]; - - NSMutableArray *newSelectionRanges = [NSMutableArray array]; - - [textFragments enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSDictionary *textFragment, NSUInteger idx, BOOL *stop) { - NSRange range = [textFragment[@"range"] rangeValue]; - NSString *newString = textFragment[@"textFragment"]; - [textStorage beginEditing]; - [textStorage replaceCharactersInRange:range withString:newString withUndoManager:[document undoManager]]; - [BBXcode normalizeCodeAtRange:NSMakeRange(range.location, newString.length) document:document]; - - // If more than one selection update previous range.locations by adding changeInLength - if (newSelectionRanges.count > 0) { - NSUInteger i = 0; - while (i < newSelectionRanges.count) { - NSRange range = [[newSelectionRanges objectAtIndex:i] rangeValue]; - range.location = range.location + [textStorage changeInLength]; - [newSelectionRanges replaceObjectAtIndex:i withObject:[NSValue valueWithRange:range]]; - i++; - } - } - - NSRange editedRange = [textStorage editedRange]; - if (editedRange.location != NSNotFound) { - [newSelectionRanges addObject:[NSValue valueWithRange:editedRange]]; - } - [textStorage endEditing]; - }]; - - if (newSelectionRanges.count > 0) { - [[BBXcode currentSourceCodeTextView] setSelectedRanges:newSelectionRanges]; - } - - BOOL codeHasChanged = (![originalString isEqualToString:textStorage.string]); - return codeHasChanged; -} - -#pragma mark - Normalizing - -+ (void)normalizeCodeAtRange:(NSRange)range document:(IDESourceCodeDocument *)document { - BBCodeFormattingScheme scheme = [[NSUserDefaults standardUserDefaults] integerForKey:BBUserDefaultsCodeFormattingScheme]; - NSLog(@"scheme %li",scheme); - if (scheme != BBCodeFormattingSchemeUncrustifyAndXCodeNormalization) return; - - DVTSourceTextStorage *textStorage = [document textStorage]; - - const NSRange scopeLineRange = [textStorage lineRangeForCharacterRange:range]; // the line range stays unchanged during the normalization - - NSRange characterRange = [textStorage characterRangeForLineRange:scopeLineRange]; - - DVTTextPreferences *preferences = [DVTTextPreferences preferences]; - - if (preferences.useSyntaxAwareIndenting) { - // PS: The method [DVTSourceTextStorage indentCharacterRange:undoManager:] always indents empty lines to the same level as code (ignoring the preferences in Xcode concerning the identation of whitespace only lines). - [textStorage indentCharacterRange:characterRange undoManager:[document undoManager]]; - characterRange = [textStorage characterRangeForLineRange:scopeLineRange]; - } - - if (preferences.trimTrailingWhitespace) { - BOOL trimTrailingWhitespace = preferences.trimTrailingWhitespace; - BOOL trimWhitespaceOnlyLines = trimTrailingWhitespace && preferences.trimWhitespaceOnlyLines; // only enabled in Xcode preferences if trimTrailingWhitespace is enabled - NSString *string = [textStorage.string substringWithRange:characterRange]; - NSString *trimString = [BBXcode stringByTrimmingString:string trimWhitespaceOnlyLines:trimWhitespaceOnlyLines trimTrailingWhitespace:trimTrailingWhitespace]; - [textStorage replaceCharactersInRange:characterRange withString:trimString withUndoManager:[document undoManager]]; - } -} - -+ (NSString *)stringByTrimmingString:(NSString *)string trimWhitespaceOnlyLines:(BOOL)trimWhitespaceOnlyLines trimTrailingWhitespace:(BOOL)trimTrailingWhitespace { - NSMutableString *mResultString = [NSMutableString string]; - - // I'm not using [NSString enumerateLinesUsingBlock:] to enumerate the string by lines because the last line of the string is ignored if it's an empty line. - NSArray *lines = [string componentsSeparatedByString:@"\n"]; - - NSCharacterSet *characterSet = [NSCharacterSet whitespaceCharacterSet]; // [NSCharacterSet whitespaceCharacterSet] means tabs or spaces - - [lines enumerateObjectsWithOptions:0 usingBlock:^(NSString *line, NSUInteger idx, BOOL *stop) { - if (idx > 0) { - [mResultString appendString:@"\n"]; - } - - BOOL acceptedLine = YES; - - NSString *trimSubstring = [line stringByTrimmingCharactersInSet:characterSet]; - - if (trimWhitespaceOnlyLines) { - acceptedLine = (trimSubstring.length > 0); - } - - if (acceptedLine) { - if (trimTrailingWhitespace && trimSubstring.length > 0) { - line = BBStringByTrimmingTrailingCharactersFromString(line, characterSet); - } - [mResultString appendString:line]; - } - }]; - - return [NSString stringWithString:mResultString]; -} - -@end diff --git a/Classes/CFOClangFormatter.h b/Classes/CFOClangFormatter.h new file mode 100644 index 0000000..830df52 --- /dev/null +++ b/Classes/CFOClangFormatter.h @@ -0,0 +1,24 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOFormatter.h" + +extern NSString * const CFOClangStyleFile; +extern NSString * const CFOClangStylePredefinedLLVM; +extern NSString * const CFOClangStylePredefinedGoogle; +extern NSString * const CFOClangStylePredefinedChromium; +extern NSString * const CFOClangStylePredefinedMozilla; +extern NSString * const CFOClangStylePredefinedWebKit; + +extern NSString * const CFOClangDumpConfigurationOptionsStyle; + +@interface CFOClangFormatter : CFOFormatter + ++ (NSArray *)predefinedStyles; ++ (NSData *)factoryStyleConfigurationBasedOnStyle:(NSString *)style error:(NSError **)error; + +@property (nonatomic) NSString *style; + +@end diff --git a/Classes/CFOClangFormatter.m b/Classes/CFOClangFormatter.m new file mode 100644 index 0000000..02a6001 --- /dev/null +++ b/Classes/CFOClangFormatter.m @@ -0,0 +1,223 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOClangFormatter.h" +#import "DiffMatchPatch.h" + +NSString * const CFOClangStyleFile = @"file"; +NSString * const CFOClangStylePredefinedLLVM = @"LLVM"; +NSString * const CFOClangStylePredefinedGoogle = @"Google"; +NSString * const CFOClangStylePredefinedChromium = @"Chromium"; +NSString * const CFOClangStylePredefinedMozilla = @"Mozilla"; +NSString * const CFOClangStylePredefinedWebKit = @"WebKit"; + +NSString * const CFOClangDumpConfigurationOptionsStyle = @"style"; + +@implementation CFOClangFormatter + +#pragma mark - Setup and Teardown + +- (id)initWithInputString:(NSString *)string presentedURL:(NSURL *)presentedURL { + self = [super initWithInputString:string presentedURL:presentedURL]; + if (self) { + _style = CFOClangStylePredefinedLLVM; + } + return self; +} + +#pragma mark - CFOFormatterProtocol + ++ (NSArray *)searchedURLsForExecutable { + return @[ + [NSURL fileURLWithPath:@"/usr/local/bin/clang-format"], + [NSURL fileURLWithPath:@"/usr/bin/clang-format"], + ]; +} + +- (NSArray *)fragmentsByFormattingInputAtRanges:(NSArray *)ranges error:(NSError **)outError { + + NSError *error = nil; + + NSURL *executableURL = [[self class] resolvedExecutableURLWithError:&error]; + if (outError) *outError = error; + + if (!executableURL) { + return nil; + } + + NSArray *normalizedRanges = [self normalizedRangesForInputRanges:ranges]; + + NSMutableArray *fragments = [NSMutableArray array]; + + NSMutableArray *args = [NSMutableArray array]; + + [args addObject:[NSString stringWithFormat:@"-assume-filename=%@",self.presentedURL.path]]; + [args addObject:[NSString stringWithFormat:@"-style=%@",self.style]]; + + for (NSValue *rangeValue in normalizedRanges) { + NSRange range = [rangeValue rangeValue]; + [args addObject:[NSString stringWithFormat:@"-offset=%lu",(unsigned long)range.location]]; + [args addObject:[NSString stringWithFormat:@"-length=%lu",(unsigned long)range.length]]; + } + + NSPipe *inputPipe = NSPipe.pipe; + NSPipe *outputPipe = NSPipe.pipe; + NSPipe *errorPipe = NSPipe.pipe; + + NSTask *task = [[NSTask alloc] init]; + task.launchPath = executableURL.path; + task.arguments = args; + + task.standardInput = inputPipe; + task.standardOutput = outputPipe; + task.standardError = errorPipe; + + [task launch]; + + [[inputPipe fileHandleForWriting] writeData:[self.inputString dataUsingEncoding:NSUTF8StringEncoding]]; + [[inputPipe fileHandleForWriting] closeFile]; + + NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile]; + NSData *errorData = [[errorPipe fileHandleForReading] readDataToEndOfFile]; + + [task waitUntilExit]; + + int status = [task terminationStatus]; + + if (status == 0) { + + if (errorData.length > 0) { + NSString *warning = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + NSLog(@"!!! Parser Warning: %@", warning); + } + + + NSString *formattedString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; + + DiffMatchPatch *dmp = [[DiffMatchPatch alloc] init]; + + + NSMutableArray *diffs = [dmp diff_mainOfOldString:self.inputString andNewString:formattedString]; + + NSUInteger j = 0; + + NSMutableArray *replacementFragments = [NSMutableArray array]; + + for (Diff *diff in diffs) { + if (diff.operation == DIFF_INSERT) { + CFOFragment *fragment = [CFOFragment fragmentWithInputRange:NSMakeRange(j, 0) string:diff.text]; + [replacementFragments addObject:fragment]; + } + else if (diff.operation == DIFF_DELETE) { + CFOFragment *fragment = [CFOFragment fragmentWithInputRange:NSMakeRange(j, diff.text.length) string:@""]; + [replacementFragments addObject:fragment]; + j += diff.text.length; + } + else if (diff.operation == DIFF_EQUAL) { + j += diff.text.length; + } + } + + if (replacementFragments.count > 0) { + NSRange modifiedRange = [replacementFragments.firstObject inputRange]; + if (replacementFragments.count > 1) { + modifiedRange.length = NSMaxRange([replacementFragments.lastObject inputRange]) - modifiedRange.location; + } + + NSRange destinationRange = modifiedRange;//NSMakeRange([replacementFragments.firstObject inputRange].location, 0); + + for (CFOFragment *fragment in replacementFragments) { + destinationRange.length += (fragment.string.length - fragment.inputRange.length); + } + + NSString *formattedSubstring = [formattedString substringWithRange:destinationRange]; + + CFOFragment *fragment = [CFOFragment fragmentWithInputRange:modifiedRange string:formattedSubstring]; + [fragments addObject:fragment]; + } + + } + else { + if (outError) { + NSString *errorString = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + if (!errorString) { + errorString = NSLocalizedString(@"Unknown Error", nil); + } + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Clang Formatter error:\n\n%@", nil), errorString]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + + return [fragments copy]; +} + +#pragma mark - CFOClangFormatter + ++ (NSArray *)predefinedStyles { + return @[CFOClangStylePredefinedLLVM, CFOClangStylePredefinedGoogle, CFOClangStylePredefinedChromium, CFOClangStylePredefinedMozilla, CFOClangStylePredefinedWebKit]; +} + ++ (NSData *)factoryStyleConfigurationBasedOnStyle:(NSString *)style error:(NSError **)outError { + NSError *error = nil; + + NSURL *executableURL = [[self class] resolvedExecutableURLWithError:&error]; + if (outError) *outError = error; + + if (!executableURL) { + return nil; + } + + NSMutableArray *args = [NSMutableArray array]; + + [args addObject:@"-dump-config"]; + if (style) { + [args addObject:[NSString stringWithFormat:@"-style=%@",style]]; + } + + NSPipe *outputPipe = NSPipe.pipe; + NSPipe *errorPipe = NSPipe.pipe; + + NSTask *task = [[NSTask alloc] init]; + task.launchPath = executableURL.path; + task.arguments = args; + + task.standardOutput = outputPipe; + task.standardError = errorPipe; + + [task launch]; + + NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile]; + NSData *errorData = [[errorPipe fileHandleForReading] readDataToEndOfFile]; + + [task waitUntilExit]; + + int status = [task terminationStatus]; + + if (status == 0) { + + if (errorData.length > 0) { + NSString *warning = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + NSLog(@"!!! Parser Warning: %@", warning); + } + + return outputData; + } + else { + if (outError) { + NSString *errorString = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + if (!errorString) { + errorString = NSLocalizedString(@"Unknown Error", nil); + } + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Clang Formatter error:\n\n%@", nil), errorString]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + + return nil; +} + +@end diff --git a/Classes/CFOFormatter.h b/Classes/CFOFormatter.h new file mode 100644 index 0000000..17f28b1 --- /dev/null +++ b/Classes/CFOFormatter.h @@ -0,0 +1,55 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import + +extern NSString * const CFOErrorDomain; + +enum { + CFOFileReadError, + CFOFormatterFailureError +}; + + +@interface CFOFragment : NSObject + +@property (nonatomic, readonly) NSRange inputRange; +@property (nonatomic, readonly) NSString *string; + ++ (instancetype)fragmentWithInputRange:(NSRange)inputRange string:(NSString *)string; + +@end + + +@protocol CFOFormatterProtocol + ++ (NSArray *)searchedURLsForExecutable; +- (NSArray *)fragmentsByFormattingInputAtRanges:(NSArray *)ranges error:(NSError **)error; + +@end + + +// CFOFormatter: An abstract "code formatter". Should not be used directly. + +@interface CFOFormatter : NSObject + +@property (nonatomic, readonly) NSString *inputString; +@property (nonatomic, readonly) NSURL *presentedURL; + ++ (NSArray *)searchedURLsForExecutable; ++ (NSURL *)resolvedExecutableURLWithError:(NSError **)outError; + +- (id)initWithInputAtURL:(NSURL *)url error:(NSError **)error; +- (id)initWithInputString:(NSString *)string presentedURL:(NSURL *)presentedURL; + +- (NSArray *)fragmentsByFormattingInputAtRanges:(NSArray *)ranges error:(NSError **)error; +- (NSString *)stringByFormattingInputAtRanges:(NSArray *)ranges error:(NSError **)error; +- (NSString *)stringByFormattingInputWithError:(NSError **)error; + +- (NSArray *)normalizedRangesForInputRanges:(NSArray *)inputRanges; + +@end + + diff --git a/Classes/CFOFormatter.m b/Classes/CFOFormatter.m new file mode 100644 index 0000000..c2a232a --- /dev/null +++ b/Classes/CFOFormatter.m @@ -0,0 +1,207 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOFormatter.h" + +NSString * const CFOErrorDomain = @"com.pragmaticcode.codeformatter"; + +NSArray * CFOSortAndMergeContinuousRanges(NSArray *ranges) { + + // this method merges the continuous ranges and sort them from the lowest to the highest + + if (ranges.count == 0) return nil; + + NSMutableIndexSet *mIndexes = [NSMutableIndexSet indexSet]; + for (NSValue *rangeValue in ranges) { + NSRange range = [rangeValue rangeValue]; + [mIndexes addIndexesInRange:range]; + } + + NSMutableArray *mergedRanges = [NSMutableArray array]; + [mIndexes enumerateRangesUsingBlock:^(NSRange range, BOOL *stop) { + [mergedRanges addObject:[NSValue valueWithRange:range]]; + }]; + return [NSArray arrayWithArray:mergedRanges]; +} + +#pragma mark - + +@implementation CFOFragment + ++ (instancetype)fragmentWithInputRange:(NSRange)inputRange string:(NSString *)string { + return [[self alloc] initWithInputRange:inputRange string:string]; +} + +- (id)initWithInputRange:(NSRange)inputRange string:(NSString *)string { + self = [super init]; + if (self) { + _inputRange = inputRange; + _string = string; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@ %p> - input range:%@, string:%@", NSStringFromClass([self class]), self, NSStringFromRange(_inputRange), _string]; +} + +@end + + +#pragma mark - + +@implementation CFOFormatter + +#pragma mark Setup and Teardown + +- (id)initWithInputAtURL:(NSURL *)url error:(NSError **)outError { + NSParameterAssert(url); + + NSError *error = nil; + NSString *inputString = [NSString stringWithContentsOfURL:url usedEncoding:nil error:&error]; + if (error){ + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Unable to read the file `%@`.", nil), url.path]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFileReadError userInfo:userInfo]; + } + return nil; + } + return [self initWithInputString:inputString presentedURL:url]; +} + +- (id)initWithInputString:(NSString *)string presentedURL:(NSURL *)presentedURL { + NSParameterAssert(string); + NSParameterAssert(presentedURL); + + self = [super init]; + if (self) { + _inputString = string; + _presentedURL = presentedURL; + } + return self; +} + +#pragma mark - Input Formatting Methods + +- (NSArray *)fragmentsByFormattingInputAtRanges:(NSArray *)ranges error:(NSError **)error { + return nil; // implemented by subclass +} + +- (NSString *)stringByFormattingInputAtRanges:(NSArray *)ranges error:(NSError **)outError { + NSError *error = nil; + NSArray *fragments = [self fragmentsByFormattingInputAtRanges:ranges error:&error]; + if (!error) { + NSMutableString *mString = [NSMutableString stringWithString:self.inputString]; + [fragments enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(CFOFragment *fragment, NSUInteger idx, BOOL *stop) { + [mString replaceCharactersInRange:fragment.inputRange withString:fragment.string]; + }]; + return [mString copy]; + } + else if (outError) { + *outError = error; + } + return nil; +} + +- (NSString *)stringByFormattingInputWithError:(NSError **)error { + return [self stringByFormattingInputAtRanges:@[[NSValue valueWithRange:NSMakeRange(0, self.inputString.length)]] error:error]; +} + + +#pragma mark - Executable + ++ (NSArray *)searchedURLsForExecutable { + return nil; // implemented by subclass +} + ++ (NSURL *)resolvedExecutableURLWithError:(NSError **)outError { + + NSArray *searchedURLs = [[self class] searchedURLsForExecutable]; + for (NSURL *url in searchedURLs) { + if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) { + return url; + } + } + + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"The Formatter binary was not found at any of these paths:\n- %@", nil), [[searchedURLs valueForKey:@"path"] componentsJoinedByString:@"\n- "]]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + + return nil; +} + +#pragma mark - Helpers for subclasses + +- (NSArray *)normalizedRangesForInputRanges:(NSArray *)inputRanges { + + NSMutableArray *lineInputRanges = [NSMutableArray array]; + + for (NSValue *inputRangeValue in inputRanges) { + NSRange range = [inputRangeValue rangeValue]; + + if (NSMaxRange(range) > self.inputString.length) { + @throw [NSException exceptionWithName:NSRangeException + reason:[NSString stringWithFormat:@"Range %@ out of bounds; string length %lu", NSStringFromRange(range), (unsigned long)self.inputString.length] + userInfo:nil]; + } + + NSRange lineRange = [self.inputString lineRangeForRange:range]; + [lineInputRanges addObject:[NSValue valueWithRange:lineRange]]; + } + + NSArray *normalizedLineRanges = CFOSortAndMergeContinuousRanges(lineInputRanges); + + if (normalizedLineRanges.count > 0) { + return [normalizedLineRanges copy]; + } + return nil; +} + +@end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Classes/CFOUncrustifyFormatter.h b/Classes/CFOUncrustifyFormatter.h new file mode 100644 index 0000000..4fc0a30 --- /dev/null +++ b/Classes/CFOUncrustifyFormatter.h @@ -0,0 +1,14 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOFormatter.h" + +@interface CFOUncrustifyFormatter : CFOFormatter + +@property (nonatomic) NSURL *configurationFileURL; + ++ (NSData *)factoryStyleConfigurationWithComments:(BOOL)includeComments error:(NSError **)error; + +@end diff --git a/Classes/CFOUncrustifyFormatter.m b/Classes/CFOUncrustifyFormatter.m new file mode 100644 index 0000000..b39c313 --- /dev/null +++ b/Classes/CFOUncrustifyFormatter.m @@ -0,0 +1,278 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOUncrustifyFormatter.h" +#import + +@implementation CFOUncrustifyFormatter + +#pragma mark - CFOFormatterProtocol + ++ (NSArray *)searchedURLsForExecutable { + return @[ + [NSURL fileURLWithPath:@"/usr/local/bin/uncrustify"], + [NSURL fileURLWithPath:@"/usr/bin/uncrustify"], + ]; +} + +- (NSArray *)fragmentsByFormattingInputAtRanges:(NSArray *)ranges error:(NSError **)outError { + + NSError *error = nil; + + NSURL *executableURL = [[self class] resolvedExecutableURLWithError:&error]; + if (outError) *outError = error; + + if (!executableURL) { + return nil; + } + + NSURL *temporaryFolderURL = [[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] URLByAppendingPathComponent:[[NSUUID UUID] UUIDString] isDirectory:YES]; + [[NSFileManager defaultManager] createDirectoryAtPath:temporaryFolderURL.path withIntermediateDirectories:YES attributes:nil error:&error]; + + if (error) { + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Unable to create the temporary folder (`%@`) for Uncrustify. Error: %@.", nil), temporaryFolderURL.path, error.localizedDescription]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + + //[[NSWorkspace sharedWorkspace] openURL:temporaryFolderURL]; + + NSArray *normalizedRanges = [self normalizedRangesForInputRanges:ranges]; + + NSMutableArray *fragments = [NSMutableArray array]; + + NSMutableArray *args = [NSMutableArray array]; + + [args addObject:@"--no-backup"]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:self.presentedURL.path]) { + NSString *uti = [[NSWorkspace sharedWorkspace] typeOfFile:self.presentedURL.path error:&error]; + if (!uti) { + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Unable to find the UTI of the file `%@`. Error: %@.", nil), self.presentedURL.path, error.localizedDescription]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + BOOL isObjectiveCFile = ([[NSWorkspace sharedWorkspace] type:uti conformsToType:(NSString *)kUTTypeObjectiveCSource] + || [[NSWorkspace sharedWorkspace] type:uti conformsToType:(NSString *)kUTTypeCHeader]); + if (isObjectiveCFile) { + [args addObjectsFromArray:@[@"-l", @"OC"]]; + } + } + + BOOL isFragmented = NO; + + if (normalizedRanges.count == 1) { + NSRange range = [normalizedRanges.firstObject rangeValue]; + isFragmented = (range.length < self.inputString.length); + } + else { + isFragmented = YES; + } + + if (isFragmented) { + [args addObject:@"--frag"]; + } + + if (self.configurationFileURL && [[NSFileManager defaultManager] fileExistsAtPath:self.configurationFileURL.path]) { + + NSURL *configurationFileURL = self.configurationFileURL; + + if (!isFragmented) { + NSString *configuration = [[NSString alloc] initWithContentsOfURL:self.configurationFileURL encoding:NSUTF8StringEncoding error:&error]; + if (!configuration) { + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Unable to load the Uncrustify configuration `%@`. Error: %@.", nil), self.configurationFileURL.path, error.localizedDescription]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + BOOL hasChanged = NO; + NSString *modifiedConfiguration = [[self class] configurationByRemovingOptions:@[@"cmt_insert_file_"] fromConfiguration:configuration hasChanged:&hasChanged]; + + if (hasChanged) { + configurationFileURL = [temporaryFolderURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.cfg", [[NSUUID UUID] UUIDString]] isDirectory:NO]; + [modifiedConfiguration writeToURL:configurationFileURL atomically:YES encoding:NSUTF8StringEncoding error:&error]; + if (error) { + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Unable to write the Uncrustify configuration to `%@`. Error: %@.", nil), configurationFileURL.path, error.localizedDescription]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + } + } + + [args addObjectsFromArray:@[@"-c", configurationFileURL.path]]; + + } + + NSString *sourceFilename = (self.presentedURL) ? self.presentedURL.lastPathComponent : @"sourcecode"; + NSURL *sourceFileURL = [temporaryFolderURL URLByAppendingPathComponent:sourceFilename isDirectory:NO]; + + [args addObject:sourceFileURL.path]; + + for (NSValue *rangeValue in normalizedRanges) { + NSRange range = [rangeValue rangeValue]; + + NSString *substring = [self.inputString substringWithRange:range]; + [substring writeToURL:sourceFileURL atomically:YES encoding:NSUTF8StringEncoding error:&error]; + if (error) { + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Unable to write the temporary source code for Uncrustify to `%@`. Error: %@.", nil), sourceFileURL.path, error.localizedDescription]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + + NSPipe *errorPipe = NSPipe.pipe; + + NSTask *task = [[NSTask alloc] init]; + task.launchPath = executableURL.path; + task.arguments = args; + + task.standardError = errorPipe; + + [task launch]; + + NSData *errorData = [[errorPipe fileHandleForReading] readDataToEndOfFile]; + + [task waitUntilExit]; + + int status = [task terminationStatus]; + + if (status == 0) { + + NSString *formattedSubstring = [NSString stringWithContentsOfURL:sourceFileURL encoding:NSUTF8StringEncoding error:&error]; + if (!formattedSubstring) { + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Unable to read the uncrustified source code `%@`. Error: %@.", nil), sourceFileURL.path, error.localizedDescription]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + + CFOFragment *fragment = [CFOFragment fragmentWithInputRange:range string:formattedSubstring]; + [fragments addObject:fragment]; + + } + else { + if (outError) { + NSString *errorString = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + if (!errorString) { + errorString = NSLocalizedString(@"Unknown error while running the formatter.", nil); + } + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Uncrustify Formatter error:\n\n%@", nil), errorString]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + [[NSFileManager defaultManager] removeItemAtURL:temporaryFolderURL error:nil]; + return nil; + } + } + + [[NSFileManager defaultManager] removeItemAtURL:temporaryFolderURL error:nil]; + + return [fragments copy]; +} + +#pragma mark - CFOUncrustifyFormatter + ++ (NSString *)configurationByRemovingOptions:(NSArray *)options fromConfiguration:(NSString *)originalConfiguration hasChanged:(BOOL *)outHasChanged { + __block BOOL hasChanged = NO; + + NSMutableString *mString = [NSMutableString string]; + + [originalConfiguration enumerateSubstringsInRange:NSMakeRange(0, originalConfiguration.length) options:NSStringEnumerationByLines usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + NSString *line = [[substring stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] lowercaseString]; + BOOL optionFound = NO; + for (NSString * option in options) { + NSRange range = [line rangeOfString:option options:NSCaseInsensitiveSearch | NSAnchoredSearch]; + optionFound = (range.location != NSNotFound); + if (optionFound) { + hasChanged = YES; + break; + } + } + if (!optionFound) { + [mString appendString:substring]; + [mString appendString:@"\n"]; + } + }]; + + if (outHasChanged != NULL) { + *outHasChanged = hasChanged; + } + + return [NSString stringWithString:mString]; +} + ++ (NSData *)factoryStyleConfigurationWithComments:(BOOL)includeComments error:(NSError **)outError { + NSError *error = nil; + + NSURL *executableURL = [[self class] resolvedExecutableURLWithError:&error]; + if (outError) *outError = error; + + if (!executableURL) { + return nil; + } + + NSMutableArray *args = [NSMutableArray array]; + + if (includeComments) { + [args addObject:@"--update-config-with-doc"]; + } + else { + [args addObject:@"--update-config"]; + } + + [args addObjectsFromArray:@[@"-c", @"/dev/null"]]; + + NSPipe *outputPipe = NSPipe.pipe; + NSPipe *errorPipe = NSPipe.pipe; + + NSTask *task = [[NSTask alloc] init]; + task.launchPath = executableURL.path; + task.arguments = args; + + task.standardOutput = outputPipe; + task.standardError = errorPipe; + + [task launch]; + + NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile]; + NSData *errorData = [[errorPipe fileHandleForReading] readDataToEndOfFile]; + + [task waitUntilExit]; + + int status = [task terminationStatus]; + + if (status == 0) { + + if (errorData.length > 0) { + NSString *warning = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + NSLog(@"!!! Parser Warning: %@", warning); + } + + return outputData; + } + else { + if (outError) { + NSString *errorString = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]; + if (!errorString) { + errorString = NSLocalizedString(@"Unknown Error", nil); + } + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : [NSString stringWithFormat:NSLocalizedString(@"Uncrustify Formatter error:\n\n%@", nil), errorString]}; + *outError = [NSError errorWithDomain:CFOErrorDomain code:CFOFormatterFailureError userInfo:userInfo]; + } + return nil; + } + + return nil; +} + +@end diff --git a/Classes/XCFClangFormatter.h b/Classes/XCFClangFormatter.h new file mode 100644 index 0000000..cb67a74 --- /dev/null +++ b/Classes/XCFClangFormatter.h @@ -0,0 +1,10 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOClangFormatter.h" + +@interface XCFClangFormatter : CFOClangFormatter + +@end diff --git a/Classes/XCFClangFormatter.m b/Classes/XCFClangFormatter.m new file mode 100644 index 0000000..ada5d5e --- /dev/null +++ b/Classes/XCFClangFormatter.m @@ -0,0 +1,27 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "XCFClangFormatter.h" + +@implementation XCFClangFormatter + ++ (NSArray *)searchedURLsForExecutable { + static NSArray *array = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *url = [bundle URLForResource:@"clang-format" withExtension:@""]; + + array = [[super searchedURLsForExecutable] arrayByAddingObject:url]; + + }); + + return array; +} + +@end + diff --git a/Classes/XCFConstants.h b/Classes/XCFConstants.h new file mode 100644 index 0000000..57d4b50 --- /dev/null +++ b/Classes/XCFConstants.h @@ -0,0 +1,14 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import + +static NSString * const XCFErrorDomain = @"com.pragmaticcode.bbuncrustifyplugin"; + +enum { + XCFFormatterMissingConfigurationError, +}; + + diff --git a/Classes/XCFDefaults.h b/Classes/XCFDefaults.h new file mode 100644 index 0000000..e0132ae --- /dev/null +++ b/Classes/XCFDefaults.h @@ -0,0 +1,26 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import + +extern NSString * const XCFDefaultsKeySelectedFormatter; +extern NSString * const XCFDefaultsFormatterValueClang; +extern NSString * const XCFDefaultsFormatterValueUncrustify; + +extern NSString * const XCFDefaultsKeyXcodeIndentingEnabled; +extern NSString * const XCFDefaultsKeyClangStyle; + +extern NSString * const XCFDefaultsKeyClangFactoryBasedStyle; +extern NSString * const XCFDefaultsClangFactoryBasedStyleValueNone; + +extern NSString * const XCFDefaultsKeyConfigurationEditorIdentifier; +extern NSString * const XCFDefaultsKeyUncrustifyXEnabled; + +@interface XCFDefaults : NSObject + ++ (void)registerDefaults; ++ (void)debug_clearPreferences; + +@end diff --git a/Classes/XCFDefaults.m b/Classes/XCFDefaults.m new file mode 100644 index 0000000..cec78a9 --- /dev/null +++ b/Classes/XCFDefaults.m @@ -0,0 +1,55 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "XCFDefaults.h" +#import "CFOClangFormatter.h" + +NSString * const XCFDefaultsKeySelectedFormatter = @"XCFSelectedFormatter"; +NSString * const XCFDefaultsFormatterValueClang = @"clang"; +NSString * const XCFDefaultsFormatterValueUncrustify = @"uncrustify"; + +NSString * const XCFDefaultsKeyXcodeIndentingEnabled = @"XCFXcodeIdentingEnabled"; +NSString * const XCFDefaultsKeyClangStyle = @"XCFClangStyle"; + +NSString * const XCFDefaultsKeyClangFactoryBasedStyle = @"XCFClangFactoryBasedStyle"; +NSString * const XCFDefaultsClangFactoryBasedStyleValueNone = @"none"; + +NSString * const XCFDefaultsKeyConfigurationEditorIdentifier = @"XCFConfigEditorIdentifier"; +NSString * const XCFDefaultsKeyUncrustifyXEnabled = @"XCFUncrustifyXEnabled"; + +@implementation XCFDefaults + ++ (NSDictionary*)defaultValues { + + return @{ + XCFDefaultsKeySelectedFormatter : XCFDefaultsFormatterValueUncrustify, + XCFDefaultsKeyXcodeIndentingEnabled : @(NO), + XCFDefaultsKeyClangStyle : CFOClangStylePredefinedLLVM, + XCFDefaultsKeyUncrustifyXEnabled : @(YES), + XCFDefaultsKeyClangFactoryBasedStyle : CFOClangStylePredefinedLLVM + }; + +} + ++ (void)registerDefaults { + + [[[self class]defaultValues] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if (![[NSUserDefaults standardUserDefaults] objectForKey:key]) { + [[NSUserDefaults standardUserDefaults] setObject:obj forKey:key]; + } + }]; + +} + ++ (void)debug_clearPreferences { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:XCFDefaultsKeySelectedFormatter]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:XCFDefaultsKeyXcodeIndentingEnabled]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:XCFDefaultsKeyClangStyle]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:XCFDefaultsKeyClangFactoryBasedStyle]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:XCFDefaultsKeyConfigurationEditorIdentifier]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:XCFDefaultsKeyUncrustifyXEnabled]; +} + +@end diff --git a/Classes/XCFFormatterUtilities.h b/Classes/XCFFormatterUtilities.h new file mode 100644 index 0000000..3fcd413 --- /dev/null +++ b/Classes/XCFFormatterUtilities.h @@ -0,0 +1,23 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOClangFormatter.h" +#import "CFOUncrustifyFormatter.h" + +@interface CFOClangFormatter(XCFAdditions) + ++ (NSURL *)configurationFileURLForPresentedURL:(NSURL *)presentedURL; + +@end + +@interface CFOUncrustifyFormatter(XCFAdditions) + ++ (NSURL *)builtinConfigurationFileURL; + ++ (NSURL *)configurationFileURLForPresentedURL:(NSURL *)presentedURL; + +@end + + diff --git a/Classes/XCFFormatterUtilities.m b/Classes/XCFFormatterUtilities.m new file mode 100644 index 0000000..ab26624 --- /dev/null +++ b/Classes/XCFFormatterUtilities.m @@ -0,0 +1,99 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "XCFFormatterUtilities.h" + +@interface XCFFormatterUtilities : NSObject + +@end + +@implementation XCFFormatterUtilities + ++ (NSURL *)configurationFileURLForPresentedURL:(NSURL *)presentedURL lookupFilenames:(NSArray *)lookupFilenames alternateURLs:(NSArray *)alternateURLs { + NSParameterAssert(presentedURL); + + NSURL *directoryURL = [presentedURL URLByDeletingLastPathComponent]; + + while(directoryURL != nil){ + + for (NSString *lookupFilename in lookupFilenames) { + NSURL *url = [directoryURL URLByAppendingPathComponent:lookupFilename isDirectory:NO]; + if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) { + return url; + } + } + + NSURL *parentURL = nil; + if(![directoryURL getResourceValue:&parentURL forKey:NSURLParentDirectoryURLKey error:nil]){ + break; + } + + directoryURL = parentURL; + } + + + + for (NSURL *alternateURL in alternateURLs) { + if ([[NSFileManager defaultManager] fileExistsAtPath:alternateURL.path]) { + return alternateURL; + } + } + + return nil; +} + +@end + +@implementation CFOClangFormatter(XCFAdditions) + ++ (NSURL *)configurationFileURLForPresentedURL:(NSURL *)presentedURL { + NSArray *lookupFilenames = @[@"_clang-format", @".clang-format"]; + + return [XCFFormatterUtilities configurationFileURLForPresentedURL:presentedURL lookupFilenames:lookupFilenames alternateURLs:nil]; +} + +@end + +@implementation CFOUncrustifyFormatter(XCFAdditions) + ++ (NSURL *)builtinConfigurationFileURL { + static NSURL *builtInConfigurationFileURL = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + builtInConfigurationFileURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"uncrustify" withExtension:@"cfg"]; + }); + return builtInConfigurationFileURL; +} + ++ (NSURL *)configurationFileURLForPresentedURL:(NSURL *)presentedURL { + + NSArray *lookupFilenames = @[@"uncrustify.cfg", @".uncrustifyconfig"]; + + static NSArray *alternateURLs = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableArray *array = [NSMutableArray array]; + + NSURL *homeDirectoryURL = [NSURL fileURLWithPath:NSHomeDirectory()]; + for (NSString *lookupFilename in lookupFilenames) { + [array addObject:[homeDirectoryURL URLByAppendingPathComponent:lookupFilename isDirectory:NO]]; + } + + [array addObject:[[homeDirectoryURL URLByAppendingPathComponent:@".uncrustify" isDirectory:YES] URLByAppendingPathComponent:@"uncrustify.cfg" isDirectory:NO]]; + + NSURL *builtInConfigurationFileURL = [[self class] builtinConfigurationFileURL]; + if (builtInConfigurationFileURL) { + [array addObject:builtInConfigurationFileURL]; + } + + alternateURLs = [array copy]; + + }); + + return [XCFFormatterUtilities configurationFileURLForPresentedURL:presentedURL lookupFilenames:lookupFilenames alternateURLs:alternateURLs]; +} + +@end diff --git a/Classes/XCFPlugin.h b/Classes/XCFPlugin.h new file mode 100755 index 0000000..141129f --- /dev/null +++ b/Classes/XCFPlugin.h @@ -0,0 +1,10 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import + +@interface XCFPlugin : NSObject + +@end diff --git a/Classes/XCFPlugin.m b/Classes/XCFPlugin.m new file mode 100755 index 0000000..dcafb29 --- /dev/null +++ b/Classes/XCFPlugin.m @@ -0,0 +1,168 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "XCFPlugin.h" +#import "XCFXcodeFormatter.h" +#import "XCFDefaults.h" +#import "XCFPreferencesWindowController.h" +#import "BBPluginUpdater.h" + +@interface XCFPlugin() +@property (nonatomic, readonly) XCFPreferencesWindowController *preferencesWindowController; +@end + +@implementation XCFPlugin {} + +@synthesize preferencesWindowController = _preferencesWindowController; + +#pragma mark - Setup and Teardown + +static XCFPlugin *sharedPlugin = nil; + ++ (void)pluginDidLoad:(NSBundle *)plugin { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedPlugin = [[self alloc] init]; + [XCFDefaults registerDefaults]; + }); +} + +- (XCFPreferencesWindowController *)preferencesWindowController { + if (!_preferencesWindowController) { + _preferencesWindowController = [[XCFPreferencesWindowController alloc] init]; + } + return _preferencesWindowController; +} + +- (id)init { + self = [super init]; + if (self) { + NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"]; + if (editMenuItem) { + [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]]; + + NSMenuItem *menuItem; + menuItem = [[NSMenuItem alloc] initWithTitle:@"Format Selected Files" action:@selector(formatSelectedFiles:) keyEquivalent:@""]; + [menuItem setTarget:self]; + [[editMenuItem submenu] addItem:menuItem]; + + menuItem = [[NSMenuItem alloc] initWithTitle:@"Format Active File" action:@selector(formatActiveFile:) keyEquivalent:@""]; + [menuItem setTarget:self]; + [[editMenuItem submenu] addItem:menuItem]; + + menuItem = [[NSMenuItem alloc] initWithTitle:@"Format Selected Lines" action:@selector(formatSelectedLines:) keyEquivalent:@""]; + [menuItem setTarget:self]; + [[editMenuItem submenu] addItem:menuItem]; + + menuItem = [[NSMenuItem alloc] initWithTitle:@"Edit Configuration" action:@selector(launchConfigurationEditor:) keyEquivalent:@""]; + [menuItem setTarget:self]; + [[editMenuItem submenu] addItem:menuItem]; + + menuItem = [[NSMenuItem alloc] initWithTitle:@"BBUncrustifyPlugin Preferences…" action:@selector(showPreferences:) keyEquivalent:@""]; + [menuItem setTarget:self]; + [[editMenuItem submenu] addItem:menuItem]; + + [BBPluginUpdater sharedUpdater].delegate = self; + + NSLog(@"BBUncrustifyPlugin (V%@) loaded", [[[NSBundle bundleForClass:[self class]] infoDictionary] objectForKey:@"CFBundleVersion"]); + } + } + return self; +} + +#pragma mark - Actions + +- (IBAction)formatSelectedFiles:(id)sender { + NSError *error = nil; + [XCFXcodeFormatter formatSelectedFilesWithError:&error]; + if (error) { + [[NSAlert alertWithError:error] runModal]; + } + [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; +} + +- (IBAction)formatActiveFile:(id)sender { + //[self.preferencesWindowController showWindow:nil]; + NSError *error = nil; + [XCFXcodeFormatter formatActiveFileWithError:&error]; + if (error) { + [[NSAlert alertWithError:error] runModal]; + } + [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; +} + +- (IBAction)formatSelectedLines:(id)sender { + NSError *error = nil; + [XCFXcodeFormatter formatSelectedLinesWithError:&error]; + if (error) { + [[NSAlert alertWithError:error] runModal]; + } + [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; +} + +- (IBAction)launchConfigurationEditor:(id)sender { + NSError *error = nil; + [XCFXcodeFormatter launchConfigurationEditorWithError:&error]; + if (error) { + if ([error.domain isEqualToString:XCFErrorDomain] && error.code == XCFFormatterMissingConfigurationError) { + + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Configuration File Not Found", nil) + defaultButton:NSLocalizedString(@"Open Preferences…", nil) + alternateButton:NSLocalizedString(@"Cancel", nil) + otherButton:nil + informativeTextWithFormat:error.localizedDescription,nil]; + + if ([alert runModal] == NSAlertDefaultReturn) { + [self.preferencesWindowController showWindow:nil]; + } + } + else { + [[NSAlert alertWithError:error] runModal]; + } + } + [[BBPluginUpdater sharedUpdater] checkForUpdatesIfNeeded]; +} + +- (IBAction)showPreferences:(id)sender { + [self.preferencesWindowController showWindow:nil]; +} + +#pragma mark - NSMenuValidation + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { + if ([menuItem action] == @selector(formatSelectedFiles:)) { + return [XCFXcodeFormatter canFormatSelectedFiles]; + } + else if ([menuItem action] == @selector(formatActiveFile:)) { + return [XCFXcodeFormatter canFormatActiveFile]; + } + else if ([menuItem action] == @selector(formatSelectedLines:)) { + return [XCFXcodeFormatter canFormatSelectedLines]; + } + else if ([menuItem action] == @selector(launchConfigurationEditor:)) { + + NSString *formatter = @""; + NSString *selectedFormatter = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeySelectedFormatter]; + if ([selectedFormatter isEqualToString:XCFDefaultsFormatterValueClang]) { + formatter = @"Clang"; + } + else if ([selectedFormatter isEqualToString:XCFDefaultsFormatterValueUncrustify]) { + formatter = @"Uncrustify"; + } + + menuItem.title = [NSString stringWithFormat:@"Edit %@ Configuration", formatter]; + + return [XCFXcodeFormatter canLaunchConfigurationEditor]; + } + return YES; +} + +#pragma mark - SUUpdater Delegate + +- (NSString *)pathToRelaunchForUpdater:(SUUpdater *)updater { + return [[NSBundle mainBundle].bundleURL path]; +} + +@end diff --git a/Classes/XCFPreferencesWindowController.h b/Classes/XCFPreferencesWindowController.h new file mode 100644 index 0000000..496ba20 --- /dev/null +++ b/Classes/XCFPreferencesWindowController.h @@ -0,0 +1,10 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import + +@interface XCFPreferencesWindowController : NSWindowController + +@end diff --git a/Classes/XCFPreferencesWindowController.m b/Classes/XCFPreferencesWindowController.m new file mode 100644 index 0000000..fa201da --- /dev/null +++ b/Classes/XCFPreferencesWindowController.m @@ -0,0 +1,283 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "XCFPreferencesWindowController.h" +#import "XCFDefaults.h" +#import "XCFClangFormatter.h" +#import "XCFUncrustifyFormatter.h" + +static NSString * const kFormatterKeyTitle = @"title"; +static NSString * const kFormatterKeyIdentifier = @"identifier"; + +static NSString * const kFormatterStyleKeyTitle = @"title"; +static NSString * const kFormatterStyleKeyIdentifier = @"identifier"; + +@interface XCFPreferencesWindowController () +@property (nonatomic, readonly) NSArray *formatters; + +@property (nonatomic, weak) IBOutlet NSPopUpButton *clangStylePopUpButton; +@property (nonatomic, weak) IBOutlet NSPopUpButton *configurationEditorPopUpButton; +@property (nonatomic, weak) IBOutlet NSView *clangFactoryConfigurationAccessoryView; +@property (nonatomic, weak) IBOutlet NSPopUpButton *clangStyleForFactoryConfigurationPopupButton; +@property (nonatomic, weak) IBOutlet NSTextField *pluginVersionTextField; + +@end + +@implementation XCFPreferencesWindowController + +@synthesize formatters = _formatters; + +- (void)awakeFromNib { + [self updateClangStyles]; + [self updateConfigurationsEditors]; + + + self.pluginVersionTextField.stringValue = [NSString stringWithFormat:@"Version %@",[[[NSBundle bundleForClass:[self class]] infoDictionary] objectForKey:@"CFBundleVersion"]]; +} + +- (NSArray *)formatters { + if (!_formatters) { + _formatters = @[ + @{kFormatterKeyTitle : @"Clang", kFormatterKeyIdentifier : XCFDefaultsFormatterValueClang}, + @{kFormatterKeyTitle : @"Uncrustify", kFormatterKeyIdentifier : XCFDefaultsFormatterValueUncrustify} + ]; + } + return _formatters; +} + + +- (void)updateClangStyles { + + NSArray *predefinedStyles = [[CFOClangFormatter predefinedStyles] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; + + [self.clangStylePopUpButton removeAllItems]; + + NSMenuItem *menuItem = nil; + + menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Predefined Styles:", nil) action:nil keyEquivalent:@""]; + [menuItem setEnabled:NO]; + [self.clangStylePopUpButton.menu addItem:menuItem]; + + for (NSString *style in predefinedStyles) { + menuItem = [[NSMenuItem alloc] initWithTitle:style action:nil keyEquivalent:@""]; + menuItem.representedObject = style; + [self.clangStylePopUpButton.menu addItem:menuItem]; + } + + [self.clangStylePopUpButton.menu addItem:[NSMenuItem separatorItem]]; + + menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Custom Style (File)", nil) action:nil keyEquivalent:@""]; + menuItem.representedObject = CFOClangStyleFile; + [self.clangStylePopUpButton.menu addItem:menuItem]; + + NSString *selectedStyle = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeyClangStyle]; + for (menuItem in self.clangStylePopUpButton.itemArray) { + if (menuItem.representedObject && [menuItem.representedObject isEqualToString:selectedStyle]) { + [self.clangStylePopUpButton selectItem:menuItem]; + break; + } + } + +} + +- (void)updateConfigurationsEditors { + + [self.configurationEditorPopUpButton removeAllItems]; + + NSArray *identifiers = CFBridgingRelease(LSCopyAllRoleHandlersForContentType((CFStringRef)@"public.text", kLSRolesAll)); + + NSString *selectedIdentifier = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeyConfigurationEditorIdentifier]; + + if (selectedIdentifier && ![identifiers containsObject:selectedIdentifier]) { + identifiers = [identifiers arrayByAddingObject:selectedIdentifier]; + } + + NSMutableArray *applications = [NSMutableArray array]; + + for (NSString *identifier in identifiers) { + NSURL *url = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:identifier]; + NSString *name = nil; + NSImage *icon = nil; + if (url) { + name = [[NSFileManager defaultManager] displayNameAtPath:url.path]; + icon = [[NSWorkspace sharedWorkspace] iconForFile:url.path]; + icon.size = NSMakeSize(16.0, 16.0); + } + if (name && icon) { + NSDictionary *dic = @{@"identifier" : identifier, @"name" : name, @"icon" : icon}; + [applications addObject:dic]; + } + } + + NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; + [applications sortUsingDescriptors:@[descriptor]]; + + for (NSDictionary *application in applications) { + NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:application[@"name"] action:nil keyEquivalent:@""]; + menuItem.image = application[@"icon"]; + menuItem.representedObject = application[@"identifier"]; + [self.configurationEditorPopUpButton.menu addItem:menuItem]; + } + + if (applications.count > 0) { + [self.configurationEditorPopUpButton.menu addItem:[NSMenuItem separatorItem]]; + } + + { + NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Select Application…", nil) action:nil keyEquivalent:@""]; + menuItem.representedObject = nil; + [self.configurationEditorPopUpButton.menu addItem:menuItem]; + } + + [self.configurationEditorPopUpButton selectItem:self.configurationEditorPopUpButton.itemArray.lastObject]; + + if (selectedIdentifier) { + for (NSMenuItem *menuItem in self.configurationEditorPopUpButton.itemArray) { + if (menuItem.representedObject && [menuItem.representedObject isEqualToString:selectedIdentifier]) { + [self.configurationEditorPopUpButton selectItem:menuItem]; + break; + } + } + } +} + +- (IBAction)downloadUncrustifXAction:(id)sender { + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/ryanmaxwell/UncrustifyX"]]; +} + + +- (IBAction)aboutPluginAction:(id)sender { + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/benoitsan/BBUncrustifyPlugin-Xcode"]]; +} + + +- (IBAction)selectConfigurationEditorAction:(NSPopUpButton *)sender { + if (sender.selectedItem.representedObject) { + [[NSUserDefaults standardUserDefaults] setObject:sender.selectedItem.representedObject forKey:XCFDefaultsKeyConfigurationEditorIdentifier]; + } + else { + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + openPanel.allowedFileTypes = @[(NSString *)kUTTypeApplication]; + openPanel.prompt = NSLocalizedString(@"Select", nil); + [openPanel beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { + if (returnCode == NSOKButton) { + NSBundle * appBundle = [NSBundle bundleWithURL:openPanel.URL]; + NSString *identifier = [appBundle bundleIdentifier]; + if (identifier) { + [[NSUserDefaults standardUserDefaults] setObject:identifier forKey:XCFDefaultsKeyConfigurationEditorIdentifier]; + } + } + [self updateConfigurationsEditors]; + }]; + } +} + +- (IBAction)selectClangStyleAction:(NSPopUpButton *)sender { + [[NSUserDefaults standardUserDefaults] setObject:sender.selectedItem.representedObject forKey:XCFDefaultsKeyClangStyle]; +} + +- (IBAction)createConfigurationFileAction:(NSPopUpButton *)sender { + if (sender.selectedItem.tag == 1) { // CLANG + NSSavePanel *savePanel = [NSSavePanel savePanel]; + savePanel.nameFieldStringValue = @"_clang-format"; + savePanel.accessoryView = self.clangFactoryConfigurationAccessoryView; + + { // Setup Accessory View + NSPopUpButton *stylePopUpButton = self.clangStyleForFactoryConfigurationPopupButton; + + [stylePopUpButton removeAllItems]; + + NSMenuItem *menuItem = nil; + + menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"None", nil) action:nil keyEquivalent:@""]; + menuItem.representedObject = XCFDefaultsClangFactoryBasedStyleValueNone; + [stylePopUpButton.menu addItem:menuItem]; + + [stylePopUpButton.menu addItem:[NSMenuItem separatorItem]]; + + NSArray *predefinedStyles = [[CFOClangFormatter predefinedStyles] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; + + for (NSString *style in predefinedStyles) { + menuItem = [[NSMenuItem alloc] initWithTitle:style action:nil keyEquivalent:@""]; + menuItem.representedObject = style; + [stylePopUpButton.menu addItem:menuItem]; + } + + NSString *selectedStyle = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeyClangFactoryBasedStyle]; + for (menuItem in stylePopUpButton.itemArray) { + if (menuItem.representedObject && [menuItem.representedObject isEqualToString:selectedStyle]) { + [stylePopUpButton selectItem:menuItem]; + break; + } + } + + } + + [savePanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { + + if (result == NSOKButton) { + + NSString *selectedStyle = self.clangStyleForFactoryConfigurationPopupButton.selectedItem.representedObject; + [[NSUserDefaults standardUserDefaults] setObject:selectedStyle forKey:XCFDefaultsKeyClangFactoryBasedStyle]; + + NSString *clangStyle = ([selectedStyle isEqualToString:XCFDefaultsClangFactoryBasedStyleValueNone]) ? nil : selectedStyle; + NSError *error = nil; + NSData *data = [XCFClangFormatter factoryStyleConfigurationBasedOnStyle:clangStyle error:&error]; + if (data) { + [data writeToURL:savePanel.URL options:NSDataWritingAtomic error:&error]; + } + + if (error) { + [[NSAlert alertWithError:error] runModal]; + } + + } + + }]; + } + else if (sender.selectedItem.tag == 2) { // Uncrustify + NSSavePanel *savePanel = [NSSavePanel savePanel]; + savePanel.nameFieldStringValue = @"uncrustify.cfg"; + + [savePanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { + + if (result == NSOKButton) { + + NSError *error = nil; + NSData *data = [XCFUncrustifyFormatter factoryStyleConfigurationWithComments:YES error:&error]; + if (data) { + [data writeToURL:savePanel.URL options:NSDataWritingAtomic error:&error]; + } + + if (error) { + [[NSAlert alertWithError:error] runModal]; + } + + } + + }]; + } +} + + +- (id)initWithWindow:(NSWindow *)window { + self = [super initWithWindow:window]; + if (self) { + // Initialization code here. + } + return self; +} + +- (NSString *)windowNibName { + return NSStringFromClass(self.class); +} + +- (void)windowDidLoad { + [super windowDidLoad]; + + // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. +} + +@end diff --git a/Classes/XCFUncrustifyFormatter.h b/Classes/XCFUncrustifyFormatter.h new file mode 100644 index 0000000..d7137ee --- /dev/null +++ b/Classes/XCFUncrustifyFormatter.h @@ -0,0 +1,10 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "CFOUncrustifyFormatter.h" + +@interface XCFUncrustifyFormatter : CFOUncrustifyFormatter + +@end diff --git a/Classes/XCFUncrustifyFormatter.m b/Classes/XCFUncrustifyFormatter.m new file mode 100644 index 0000000..e81fd7e --- /dev/null +++ b/Classes/XCFUncrustifyFormatter.m @@ -0,0 +1,26 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "XCFUncrustifyFormatter.h" + +@implementation XCFUncrustifyFormatter + ++ (NSArray *)searchedURLsForExecutable { + static NSArray *array = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *url = [bundle URLForResource:@"uncrustify" withExtension:@""]; + + array = [[super searchedURLsForExecutable] arrayByAddingObject:url]; + + }); + + return array; +} + +@end diff --git a/Classes/XCFXcodeFormatter.h b/Classes/XCFXcodeFormatter.h new file mode 100644 index 0000000..b1843d4 --- /dev/null +++ b/Classes/XCFXcodeFormatter.h @@ -0,0 +1,23 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import +#import "XCFConstants.h" + +@interface XCFXcodeFormatter : NSObject + ++ (BOOL)canFormatSelectedFiles; ++ (void)formatSelectedFilesWithError:(NSError **)outError; + ++ (BOOL)canFormatActiveFile; ++ (void)formatActiveFileWithError:(NSError **)outError; + ++ (BOOL)canFormatSelectedLines; ++ (void)formatSelectedLinesWithError:(NSError **)outError; + ++ (BOOL)canLaunchConfigurationEditor; ++ (void)launchConfigurationEditorWithError:(NSError **)outError; + +@end diff --git a/Classes/XCFXcodeFormatter.m b/Classes/XCFXcodeFormatter.m new file mode 100644 index 0000000..a374f0f --- /dev/null +++ b/Classes/XCFXcodeFormatter.m @@ -0,0 +1,543 @@ +// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. +// + +#import "XCFXcodeFormatter.h" +#import "XCFXcodePrivate.h" +#import "XCFClangFormatter.h" +#import "XCFUncrustifyFormatter.h" +#import "XCFFormatterUtilities.h" +#import "XCFDefaults.h" + +static NSString * const XCFUncrustifyXIdentifier = @"nz.co.xwell.UncrustifyX"; + +NSString * XCFStringByTrimmingTrailingCharactersFromString(NSString *string, NSCharacterSet *characterSet) { + NSRange rangeOfLastWantedCharacter = [string rangeOfCharacterFromSet:[characterSet invertedSet] options:NSBackwardsSearch]; + if (rangeOfLastWantedCharacter.location == NSNotFound) return @""; + return [string substringToIndex:rangeOfLastWantedCharacter.location + 1]; +} + +@implementation XCFXcodeFormatter {} + +#pragma mark - Formatter + ++ (BOOL)canFormatSelectedFiles { + NSArray *selectedFiles = [XCFXcodeFormatter selectedSourceCodeFileNavigableItems]; + return (selectedFiles.count > 0); +} + ++ (void)formatSelectedFilesWithError:(NSError **)outError { + NSArray *fileNavigableItems = [XCFXcodeFormatter selectedSourceCodeFileNavigableItems]; + IDEWorkspace *currentWorkspace = [XCFXcodeFormatter currentWorkspaceDocument].workspace; + for (IDEFileNavigableItem *fileNavigableItem in fileNavigableItems) { + NSError *error = nil; + NSDocument *document = [IDEDocumentController retainedEditorDocumentForNavigableItem:fileNavigableItem error:nil]; + if ([document isKindOfClass:NSClassFromString(@"IDESourceCodeDocument")]) { + IDESourceCodeDocument *sourceCodeDocument = (IDESourceCodeDocument *)document; + [XCFXcodeFormatter uncrustifyCodeOfDocument:sourceCodeDocument inWorkspace:currentWorkspace error:&error]; + //[document saveDocument:nil]; + } + [IDEDocumentController releaseEditorDocument:document]; + + if (error) { + if (outError) { + *outError = error; + } + break; + } + } +} + ++ (BOOL)canFormatActiveFile { + IDESourceCodeDocument *document = [XCFXcodeFormatter currentSourceCodeDocument]; + return (document != nil); +} + ++ (void)formatActiveFileWithError:(NSError **)outError { + + IDESourceCodeDocument *document = [XCFXcodeFormatter currentSourceCodeDocument]; + if (!document) return; + + NSTextView *textView = [XCFXcodeFormatter currentSourceCodeTextView]; + + DVTSourceTextStorage *textStorage = [document textStorage]; + + // We try to restore the original cursor position after the uncrustification. We compute a percentage value + // expressing the actual selected line compared to the total number of lines of the document. After the uncrustification, + // we restore the position taking into account the modified number of lines of the document. + + NSRange originalCharacterRange = [textView selectedRange]; + NSRange originalLineRange = [textStorage lineRangeForCharacterRange:originalCharacterRange]; + NSRange originalDocumentLineRange = [textStorage lineRangeForCharacterRange:NSMakeRange(0, textStorage.string.length)]; + + CGFloat verticalRelativePosition = (CGFloat)originalLineRange.location / (CGFloat)originalDocumentLineRange.length; + + IDEWorkspace *currentWorkspace = [XCFXcodeFormatter currentWorkspaceDocument].workspace; + + [XCFXcodeFormatter uncrustifyCodeOfDocument:document inWorkspace:currentWorkspace error:outError]; + + NSRange newDocumentLineRange = [textStorage lineRangeForCharacterRange:NSMakeRange(0, textStorage.string.length)]; + NSUInteger restoredLine = roundf(verticalRelativePosition * (CGFloat)newDocumentLineRange.length); + + NSRange newCharacterRange = [textStorage characterRangeForLineRange:NSMakeRange(restoredLine, 0)]; + + if (newCharacterRange.location < textStorage.string.length) { + [[XCFXcodeFormatter currentSourceCodeTextView] setSelectedRanges:@[[NSValue valueWithRange:newCharacterRange]]]; + [textView scrollRangeToVisible:newCharacterRange]; + } +} + ++ (BOOL)canFormatSelectedLines { + BOOL validated = NO; + IDESourceCodeDocument *document = [XCFXcodeFormatter currentSourceCodeDocument]; + NSTextView *textView = [XCFXcodeFormatter currentSourceCodeTextView]; + if (document && textView) { + NSArray *selectedRanges = [textView selectedRanges]; + validated = (selectedRanges.count > 0); + } + return validated; +} + ++ (void)formatSelectedLinesWithError:(NSError **)outError { + IDESourceCodeDocument *document = [XCFXcodeFormatter currentSourceCodeDocument]; + NSTextView *textView = [XCFXcodeFormatter currentSourceCodeTextView]; + if (!document || !textView) return; + IDEWorkspace *currentWorkspace = [XCFXcodeFormatter currentWorkspaceDocument].workspace; + NSArray *selectedRanges = [textView selectedRanges]; + [XCFXcodeFormatter uncrustifyCodeAtRanges:selectedRanges document:document inWorkspace:currentWorkspace error:outError]; +} + +#pragma mark Formatting + ++ (CFOFormatter *)formatterForString:(NSString *)inputString presentedURL:(NSURL *)presentedURL error:(NSError **)outError { + + NSString *selectedFormatter = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeySelectedFormatter]; + if ([selectedFormatter isEqualToString:XCFDefaultsFormatterValueClang]) { + XCFClangFormatter *formatter = [[XCFClangFormatter alloc] initWithInputString:inputString presentedURL:presentedURL]; + formatter.style = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeyClangStyle]; + return formatter; + } + else if ([selectedFormatter isEqualToString:XCFDefaultsFormatterValueUncrustify]) { + XCFUncrustifyFormatter *formatter = [[XCFUncrustifyFormatter alloc] initWithInputString:inputString presentedURL:presentedURL]; + formatter.configurationFileURL = [XCFUncrustifyFormatter configurationFileURLForPresentedURL:presentedURL]; + + if (!formatter.configurationFileURL) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : @"No configuration file was found for Uncrustify. To create a configuration file, open the Preferences."}; + NSError *error = [NSError errorWithDomain:XCFErrorDomain code:XCFFormatterMissingConfigurationError userInfo:userInfo]; + if (outError) { + *outError = error; + } + return nil; + } + + return formatter; + } + else NSAssert(NO, @"Missing case"); + return nil; +} + ++ (BOOL)uncrustifyCodeOfDocument:(IDESourceCodeDocument *)document inWorkspace:(IDEWorkspace *)workspace error:(NSError **)outError { + + NSError *error = nil; + + DVTSourceTextStorage *textStorage = [document textStorage]; + + NSString *originalString = [NSString stringWithString:textStorage.string]; + + if (textStorage.string.length > 0) { + CFOFormatter *formatter = [[self class] formatterForString:textStorage.string presentedURL:document.fileURL error:&error]; + NSString *formattedCode = [formatter stringByFormattingInputWithError:&error]; + if (formattedCode) { + [textStorage beginEditing]; + if (![formattedCode isEqualToString:textStorage.string]) { + [textStorage replaceCharactersInRange:NSMakeRange(0, textStorage.string.length) withString:formattedCode withUndoManager:[document undoManager]]; + } + [XCFXcodeFormatter normalizeCodeAtRange:NSMakeRange(0, textStorage.string.length) document:document]; + [textStorage endEditing]; + } + } + + if (error && outError) { + *outError = error; + } + + BOOL codeHasChanged = (originalString && ![originalString isEqualToString:textStorage.string]); + return codeHasChanged; +} + ++ (BOOL)uncrustifyCodeAtRanges:(NSArray *)ranges document:(IDESourceCodeDocument *)document inWorkspace:(IDEWorkspace *)workspace error:(NSError **)outError { + DVTSourceTextStorage *textStorage = [document textStorage]; + + NSError *error = nil; + + CFOFormatter *formatter = [[self class] formatterForString:textStorage.string presentedURL:document.fileURL error:&error]; + NSArray *fragments = [formatter fragmentsByFormattingInputAtRanges:ranges error:&error]; + + NSString *originalString = [NSString stringWithString:textStorage.string]; + + if (fragments) { + + NSMutableArray *newSelectionRanges = [NSMutableArray array]; + + [fragments enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(CFOFragment *fragment, NSUInteger idx, BOOL *stop) { + [textStorage beginEditing]; + [textStorage replaceCharactersInRange:fragment.inputRange withString:fragment.string withUndoManager:[document undoManager]]; + [XCFXcodeFormatter normalizeCodeAtRange:NSMakeRange(fragment.inputRange.location, fragment.string.length) document:document]; + + // If more than one selection update previous range.locations by adding changeInLength + if (newSelectionRanges.count > 0) { + NSUInteger i = 0; + while (i < newSelectionRanges.count) { + NSRange range = [[newSelectionRanges objectAtIndex:i] rangeValue]; + range.location = range.location + [textStorage changeInLength]; + [newSelectionRanges replaceObjectAtIndex:i withObject:[NSValue valueWithRange:range]]; + i++; + } + } + + NSRange editedRange = [textStorage editedRange]; + if (editedRange.location != NSNotFound) { + [newSelectionRanges addObject:[NSValue valueWithRange:editedRange]]; + } + [textStorage endEditing]; + }]; + + if (newSelectionRanges.count > 0) { + [[XCFXcodeFormatter currentSourceCodeTextView] setSelectedRanges:newSelectionRanges]; + } + +// NSString *selectedFormatter = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeySelectedFormatter]; +// if (0 && [selectedFormatter isEqualToString:XCFDefaultsFormatterValueClang]) { +// NSArray *normalizedRanges = [formatter normalizedRangesForInputRanges:ranges]; +// NSMutableArray *fixedRanges = [NSMutableArray array]; +// for (NSValue *normalizedRangeValue in normalizedRanges) { +// NSRange normalizedRange = [normalizedRangeValue rangeValue]; +// normalizedRange.length = MIN(normalizedRange.length, textStorage.string.length - normalizedRange.location); +// [fixedRanges addObject:normalizedRangeValue]; +// } +// if (fixedRanges.count > 0) { +// [[XCFXcodeFormatter currentSourceCodeTextView] setSelectedRanges:fixedRanges]; +// [[XCFXcodeFormatter currentSourceCodeTextView]scrollRangeToVisible:[fixedRanges.firstObject rangeValue]]; +// } +// } +// else { +// if (newSelectionRanges.count > 0) { +// [[XCFXcodeFormatter currentSourceCodeTextView] setSelectedRanges:newSelectionRanges]; +// } +// } + } + + if (error && outError) { + *outError = error; + } + + BOOL codeHasChanged = (![originalString isEqualToString:textStorage.string]); + return codeHasChanged; +} + +#pragma mark Normalizing Formatting + ++ (void)normalizeCodeAtRange:(NSRange)range document:(IDESourceCodeDocument *)document { + + BOOL shouldNormalize = [[NSUserDefaults standardUserDefaults] boolForKey:XCFDefaultsKeyXcodeIndentingEnabled]; + + if (!shouldNormalize) return; + + DVTSourceTextStorage *textStorage = [document textStorage]; + + const NSRange scopeLineRange = [textStorage lineRangeForCharacterRange:range]; // the line range stays unchanged during the normalization + + NSRange characterRange = [textStorage characterRangeForLineRange:scopeLineRange]; + + DVTTextPreferences *preferences = [DVTTextPreferences preferences]; + + if (preferences.useSyntaxAwareIndenting) { + // PS: The method [DVTSourceTextStorage indentCharacterRange:undoManager:] always indents empty lines to the same level as code (ignoring the preferences in Xcode concerning the identation of whitespace only lines). + [textStorage indentCharacterRange:characterRange undoManager:[document undoManager]]; + characterRange = [textStorage characterRangeForLineRange:scopeLineRange]; + } + + if (preferences.trimTrailingWhitespace) { + BOOL trimTrailingWhitespace = preferences.trimTrailingWhitespace; + BOOL trimWhitespaceOnlyLines = trimTrailingWhitespace && preferences.trimWhitespaceOnlyLines; // only enabled in Xcode preferences if trimTrailingWhitespace is enabled + NSString *string = [textStorage.string substringWithRange:characterRange]; + NSString *trimString = [XCFXcodeFormatter stringByTrimmingString:string trimWhitespaceOnlyLines:trimWhitespaceOnlyLines trimTrailingWhitespace:trimTrailingWhitespace]; + [textStorage replaceCharactersInRange:characterRange withString:trimString withUndoManager:[document undoManager]]; + } +} + ++ (NSString *)stringByTrimmingString:(NSString *)string trimWhitespaceOnlyLines:(BOOL)trimWhitespaceOnlyLines trimTrailingWhitespace:(BOOL)trimTrailingWhitespace { + NSMutableString *mResultString = [NSMutableString string]; + + // I'm not using [NSString enumerateLinesUsingBlock:] to enumerate the string by lines because the last line of the string is ignored if it's an empty line. + NSArray *lines = [string componentsSeparatedByString:@"\n"]; + + NSCharacterSet *characterSet = [NSCharacterSet whitespaceCharacterSet]; // [NSCharacterSet whitespaceCharacterSet] means tabs or spaces + + [lines enumerateObjectsWithOptions:0 usingBlock:^(NSString *line, NSUInteger idx, BOOL *stop) { + if (idx > 0) { + [mResultString appendString:@"\n"]; + } + + BOOL acceptedLine = YES; + + NSString *trimSubstring = [line stringByTrimmingCharactersInSet:characterSet]; + + if (trimWhitespaceOnlyLines) { + acceptedLine = (trimSubstring.length > 0); + } + + if (acceptedLine) { + if (trimTrailingWhitespace && trimSubstring.length > 0) { + line = XCFStringByTrimmingTrailingCharactersFromString(line, characterSet); + } + [mResultString appendString:line]; + } + }]; + + return [NSString stringWithString:mResultString]; +} + +#pragma mark - Configuration Editor + ++ (BOOL)canOpenApplicationWithIdentifier:(NSString *)identifier { + BOOL appExists = NO; + NSURL *appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:identifier]; + if (appURL) appExists = [[NSFileManager defaultManager] fileExistsAtPath:appURL.path]; + return appExists; +} + ++ (BOOL)canLaunchConfigurationEditor { + IDESourceCodeDocument *document = [XCFXcodeFormatter currentSourceCodeDocument]; + return (document != nil); +} + ++ (void)launchConfigurationEditorWithError:(NSError **)outError { + + IDESourceCodeDocument *document = [XCFXcodeFormatter currentSourceCodeDocument]; + if (!document) return; + + NSURL *configurationFileURL = nil; + + NSString *selectedFormatter = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeySelectedFormatter]; + + if ([selectedFormatter isEqualToString:XCFDefaultsFormatterValueClang]) { + configurationFileURL = [XCFClangFormatter configurationFileURLForPresentedURL:document.fileURL]; + } + else if ([selectedFormatter isEqualToString:XCFDefaultsFormatterValueUncrustify]) { + configurationFileURL = [XCFUncrustifyFormatter configurationFileURLForPresentedURL:document.fileURL]; + if ([configurationFileURL isEqualTo:[XCFUncrustifyFormatter builtinConfigurationFileURL]]) { + configurationFileURL = nil; // forbid to edit the configuration file in the plugin bundle + } + } + else { + return; + } + + if (!configurationFileURL) { + if (outError) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : @"No configuration file was found. To create a configuration file, open the Preferences."}; + *outError = [NSError errorWithDomain:XCFErrorDomain code:XCFFormatterMissingConfigurationError userInfo:userInfo]; + } + return; + } + + if ([selectedFormatter isEqualToString:XCFDefaultsFormatterValueUncrustify] + && [[NSUserDefaults standardUserDefaults] boolForKey:XCFDefaultsKeyUncrustifyXEnabled] + && [[self class] canOpenApplicationWithIdentifier:XCFUncrustifyXIdentifier]) { + + IDESourceCodeDocument *document = [XCFXcodeFormatter currentSourceCodeDocument]; + if (document) { + DVTSourceTextStorage *textStorage = [document textStorage]; + [[NSPasteboard pasteboardWithName:@"BBUncrustifyPlugin-source-code"] clearContents]; + if (textStorage.string) { + [[NSPasteboard pasteboardWithName:@"BBUncrustifyPlugin-source-code"] writeObjects:@[textStorage.string]]; + } + } + NSDictionary *configuration = @{ NSWorkspaceLaunchConfigurationArguments: @[@"-bbuncrustifyplugin", @"-configpath", configurationFileURL.path] }; + NSURL *appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:XCFUncrustifyXIdentifier]; + + [[NSWorkspace sharedWorkspace]launchApplicationAtURL:appURL options:0 configuration:configuration error:nil]; + } + else { + BOOL succeeds = NO; + + NSString *editorIdentifier = [[NSUserDefaults standardUserDefaults] stringForKey:XCFDefaultsKeyConfigurationEditorIdentifier]; + if (editorIdentifier) { + succeeds = [[NSWorkspace sharedWorkspace] openURLs:@[configurationFileURL] withAppBundleIdentifier:editorIdentifier options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifiers:nil]; + } + + if (!succeeds) { + [[NSWorkspace sharedWorkspace] openURL:configurationFileURL]; + } + + } +} + +#pragma mark - Helpers + ++ (id)currentEditor { + NSWindowController *currentWindowController = [[NSApp keyWindow] windowController]; + if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { + IDEWorkspaceWindowController *workspaceController = (IDEWorkspaceWindowController *)currentWindowController; + IDEEditorArea *editorArea = [workspaceController editorArea]; + IDEEditorContext *editorContext = [editorArea lastActiveEditorContext]; + return [editorContext editor]; + } + return nil; +} + ++ (IDEWorkspaceDocument *)currentWorkspaceDocument { + NSWindowController *currentWindowController = [[NSApp keyWindow] windowController]; + id document = [currentWindowController document]; + if (currentWindowController && [document isKindOfClass:NSClassFromString(@"IDEWorkspaceDocument")]) { + return (IDEWorkspaceDocument *)document; + } + return nil; +} + ++ (IDESourceCodeDocument *)currentSourceCodeDocument { + if ([[XCFXcodeFormatter currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) { + IDESourceCodeEditor *editor = [XCFXcodeFormatter currentEditor]; + return editor.sourceCodeDocument; + } + + if ([[XCFXcodeFormatter currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) { + IDESourceCodeComparisonEditor *editor = [XCFXcodeFormatter currentEditor]; + if ([[editor primaryDocument] isKindOfClass:NSClassFromString(@"IDESourceCodeDocument")]) { + IDESourceCodeDocument *document = (IDESourceCodeDocument *)editor.primaryDocument; + return document; + } + } + + return nil; +} + ++ (NSTextView *)currentSourceCodeTextView { + if ([[XCFXcodeFormatter currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) { + IDESourceCodeEditor *editor = [XCFXcodeFormatter currentEditor]; + return editor.textView; + } + + if ([[XCFXcodeFormatter currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) { + IDESourceCodeComparisonEditor *editor = [XCFXcodeFormatter currentEditor]; + return editor.keyTextView; + } + + return nil; +} + ++ (NSArray *)selectedNavigableItems { + NSMutableArray *mutableArray = [NSMutableArray array]; + id currentWindowController = [[NSApp keyWindow] windowController]; + if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { + IDEWorkspaceWindowController *workspaceController = currentWindowController; + IDEWorkspaceTabController *workspaceTabController = [workspaceController activeWorkspaceTabController]; + IDENavigatorArea *navigatorArea = [workspaceTabController navigatorArea]; + id currentNavigator = [navigatorArea currentNavigator]; + + if ([currentNavigator isKindOfClass:NSClassFromString(@"IDEStructureNavigator")]) { + IDEStructureNavigator *structureNavigator = currentNavigator; + for (id selectedObject in structureNavigator.selectedObjects) { + if ([selectedObject isKindOfClass:NSClassFromString(@"IDENavigableItem")]) { + [mutableArray addObject:selectedObject]; + } + } + } + } + + if (mutableArray.count) { + return [NSArray arrayWithArray:mutableArray]; + } + return nil; +} + ++ (NSArray *)selectedSourceCodeFileNavigableItems { + NSMutableArray *mutableArray = [NSMutableArray array]; + id currentWindowController = [[NSApp keyWindow] windowController]; + if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { + IDEWorkspaceWindowController *workspaceController = currentWindowController; + IDEWorkspaceTabController *workspaceTabController = [workspaceController activeWorkspaceTabController]; + IDENavigatorArea *navigatorArea = [workspaceTabController navigatorArea]; + id currentNavigator = [navigatorArea currentNavigator]; + + if ([currentNavigator isKindOfClass:NSClassFromString(@"IDEStructureNavigator")]) { + IDEStructureNavigator *structureNavigator = currentNavigator; + for (id selectedObject in structureNavigator.selectedObjects) { + NSArray *arrayOfFiles = [self recursivlyCollectFileNavigableItemsFrom:selectedObject]; + if (arrayOfFiles.count) { + [mutableArray addObjectsFromArray:arrayOfFiles]; + } + } + } + } + + if (mutableArray.count) { + return [NSArray arrayWithArray:mutableArray]; + } + return nil; +} + ++ (NSArray *)recursivlyCollectFileNavigableItemsFrom:(IDENavigableItem *)selectedObject { + id items = nil; + + if ([selectedObject isKindOfClass:NSClassFromString(@"IDEGroupNavigableItem")]) { + //|| [selectedObject isKindOfClass:NSClassFromString(@"IDEContainerFileReferenceNavigableItem")]) { //disallow project + NSMutableArray *mItems = [NSMutableArray array]; + IDEGroupNavigableItem *groupNavigableItem = (IDEGroupNavigableItem *)selectedObject; + for (IDENavigableItem *child in groupNavigableItem.childItems) { + NSArray *childItems = [self recursivlyCollectFileNavigableItemsFrom:child]; + if (childItems.count) { + [mItems addObjectsFromArray:childItems]; + } + } + items = mItems; + } + else if ([selectedObject isKindOfClass:NSClassFromString(@"IDEFileNavigableItem")]) { + IDEFileNavigableItem *fileNavigableItem = (IDEFileNavigableItem *)selectedObject; + NSString *uti = fileNavigableItem.documentType.identifier; + if ([[NSWorkspace sharedWorkspace] type:uti conformsToType:(NSString *)kUTTypeSourceCode]) { + items = @[fileNavigableItem]; + } + } + + return items; +} + ++ (NSArray *)containerFolderURLsForNavigableItem:(IDENavigableItem *)navigableItem { + NSMutableArray *mArray = [NSMutableArray array]; + + do { + NSURL *folderURL = nil; + id representedObject = navigableItem.representedObject; + if ([navigableItem isKindOfClass:NSClassFromString(@"IDEGroupNavigableItem")]) { + // IDE-GROUP (a folder in the navigator) + IDEGroup *group = (IDEGroup *)representedObject; + folderURL = group.resolvedFilePath.fileURL; + } else if ([navigableItem isKindOfClass:NSClassFromString(@"IDEContainerFileReferenceNavigableItem")]) { + // CONTAINER (an Xcode project) + IDEFileReference *fileReference = representedObject; + folderURL = [fileReference.resolvedFilePath.fileURL URLByDeletingLastPathComponent]; + } else if ([navigableItem isKindOfClass:NSClassFromString(@"IDEKeyDrivenNavigableItem")]) { + // WORKSPACE (root: Xcode project or workspace) + IDEWorkspace *workspace = representedObject; + folderURL = [workspace.representingFilePath.fileURL URLByDeletingLastPathComponent]; + } + if (folderURL && ![mArray containsObject:folderURL]) [mArray addObject:folderURL]; + navigableItem = [navigableItem parentItem]; + } while (navigableItem != nil); + + if (mArray.count > 0) return [NSArray arrayWithArray:mArray]; + return nil; +} + ++ (NSArray *)containerFolderURLsAncestorsToNavigableItem:(IDENavigableItem *)navigableItem { + if (navigableItem) { + return [XCFXcodeFormatter containerFolderURLsForNavigableItem:navigableItem]; + } + return nil; +} + + +@end diff --git a/Classes/BBXcode.h b/Classes/XCFXcodePrivate.h similarity index 75% rename from Classes/BBXcode.h rename to Classes/XCFXcodePrivate.h index 5852f9b..90917b7 100644 --- a/Classes/BBXcode.h +++ b/Classes/XCFXcodePrivate.h @@ -1,20 +1,10 @@ // -// BBXcode.h -// BBUncrustifyPlugin -// -// Created by Benoît on 16/03/13. -// +// Created by Benoît on 11/01/14. +// Copyright (c) 2014 Pragmatic Code. All rights reserved. // #import -extern NSString * const BBUserDefaultsCodeFormattingScheme; - -typedef NS_ENUM (NSUInteger, BBCodeFormattingScheme) { - BBCodeFormattingSchemeUncrustifyAndXCodeNormalization = 0, // code is formatted using uncrustify program and then normalized following Xcode preferences. - BBCodeFormattingSchemeUncrustify = 1 // code is formatted using uncrustify program. -}; - @interface DVTTextDocumentLocation : NSObject @property (readonly) NSRange characterRange; @property (readonly) NSRange lineRange; @@ -126,15 +116,4 @@ typedef NS_ENUM (NSUInteger, BBCodeFormattingScheme) { @interface IDEWorkspaceDocument : NSDocument @property (readonly) IDEWorkspace *workspace; -@end - -@interface BBXcode : NSObject -+ (IDEWorkspaceDocument *)currentWorkspaceDocument; -+ (IDESourceCodeDocument *)currentSourceCodeDocument; -+ (NSTextView *)currentSourceCodeTextView; -+ (NSArray *)selectedSourceCodeFileNavigableItems; -+ (NSArray *)selectedNavigableItems; -+ (NSArray *)containerFolderURLsAncestorsToNavigableItem:(IDENavigableItem *)navigableItem; -+ (BOOL)uncrustifyCodeOfDocument:(IDESourceCodeDocument *)document inWorkspace:(IDEWorkspace *)workspace; -+ (BOOL)uncrustifyCodeAtRanges:(NSArray *)ranges document:(IDESourceCodeDocument *)document inWorkspace:(IDEWorkspace *)workspace; -@end +@end \ No newline at end of file diff --git a/Sparkle.framework/Headers b/Externals/Sparkle.framework/Headers similarity index 100% rename from Sparkle.framework/Headers rename to Externals/Sparkle.framework/Headers diff --git a/Sparkle.framework/Resources b/Externals/Sparkle.framework/Resources similarity index 100% rename from Sparkle.framework/Resources rename to Externals/Sparkle.framework/Resources diff --git a/Sparkle.framework/Sparkle b/Externals/Sparkle.framework/Sparkle similarity index 100% rename from Sparkle.framework/Sparkle rename to Externals/Sparkle.framework/Sparkle diff --git a/Sparkle.framework/Versions/A/Headers/SUAppcast.h b/Externals/Sparkle.framework/Versions/A/Headers/SUAppcast.h similarity index 100% rename from Sparkle.framework/Versions/A/Headers/SUAppcast.h rename to Externals/Sparkle.framework/Versions/A/Headers/SUAppcast.h diff --git a/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h b/Externals/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h similarity index 100% rename from Sparkle.framework/Versions/A/Headers/SUAppcastItem.h rename to Externals/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h diff --git a/Sparkle.framework/Versions/A/Headers/SUUpdater.h b/Externals/Sparkle.framework/Versions/A/Headers/SUUpdater.h similarity index 100% rename from Sparkle.framework/Versions/A/Headers/SUUpdater.h rename to Externals/Sparkle.framework/Versions/A/Headers/SUUpdater.h diff --git a/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h b/Externals/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h similarity index 100% rename from Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h rename to Externals/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h diff --git a/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h b/Externals/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h similarity index 100% rename from Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h rename to Externals/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h diff --git a/Sparkle.framework/Versions/A/Headers/Sparkle.h b/Externals/Sparkle.framework/Versions/A/Headers/Sparkle.h similarity index 100% rename from Sparkle.framework/Versions/A/Headers/Sparkle.h rename to Externals/Sparkle.framework/Versions/A/Headers/Sparkle.h diff --git a/Sparkle.framework/Versions/A/Resources/Info.plist b/Externals/Sparkle.framework/Versions/A/Resources/Info.plist similarity index 100% rename from Sparkle.framework/Versions/A/Resources/Info.plist rename to Externals/Sparkle.framework/Versions/A/Resources/Info.plist diff --git a/Sparkle.framework/Versions/A/Resources/License.txt b/Externals/Sparkle.framework/Versions/A/Resources/License.txt similarity index 100% rename from Sparkle.framework/Versions/A/Resources/License.txt rename to Externals/Sparkle.framework/Versions/A/Resources/License.txt diff --git a/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist b/Externals/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist similarity index 100% rename from Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist rename to Externals/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist diff --git a/Sparkle.framework/Versions/A/Resources/SUStatus.nib b/Externals/Sparkle.framework/Versions/A/Resources/SUStatus.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/SUStatus.nib rename to Externals/Sparkle.framework/Versions/A/Resources/SUStatus.nib diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUPasswordPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUPasswordPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ar.lproj/SUPasswordPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUPasswordPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUPasswordPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUPasswordPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/en.lproj/SUPasswordPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUPasswordPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Info.plist b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Info.plist similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Info.plist rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Info.plist diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/MacOS/finish_installation b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/MacOS/finish_installation similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/MacOS/finish_installation rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/MacOS/finish_installation diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/PkgInfo b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/PkgInfo similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/PkgInfo rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/PkgInfo diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/SUStatus.nib b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/SUStatus.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/SUStatus.nib rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/SUStatus.nib diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/Sparkle.icns b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/Sparkle.icns similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/Sparkle.icns rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/Sparkle.icns diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ar.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ar.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ar.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ar.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/cs.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/cs.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/cs.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/cs.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/da.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/da.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/da.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/da.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/de.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/de.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/de.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/de.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/en.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/en.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/en.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/en.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/es.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/es.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/es.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/es.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/fr.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/fr.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/fr.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/fr.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/is.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/is.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/is.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/is.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/it.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/it.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/it.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/it.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ja.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ja.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ja.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ja.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/nl.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/nl.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/nl.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/nl.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pl.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pl.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pl.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pl.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_BR.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_BR.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_BR.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_BR.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_PT.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_PT.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_PT.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/pt_PT.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ro.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ro.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ro.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ro.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ru.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ru.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ru.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/ru.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sl.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sl.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sl.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sl.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sv.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sv.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sv.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/sv.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/th.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/th.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/th.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/th.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/tr.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/tr.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/tr.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/tr.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/uk.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/uk.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/uk.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/uk.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_CN.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_CN.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_CN.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_CN.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_TW.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_TW.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_TW.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/finish_installation.app/Contents/Resources/zh_TW.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/fr_CA.lproj b/Externals/Sparkle.framework/Versions/A/Resources/fr_CA.lproj similarity index 100% rename from Sparkle.framework/Versions/A/Resources/fr_CA.lproj rename to Externals/Sparkle.framework/Versions/A/Resources/fr_CA.lproj diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/pt.lproj b/Externals/Sparkle.framework/Versions/A/Resources/pt.lproj similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt.lproj rename to Externals/Sparkle.framework/Versions/A/Resources/pt.lproj diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUPasswordPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUPasswordPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUPasswordPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUPasswordPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUPasswordPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUPasswordPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/th.lproj/SUPasswordPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUPasswordPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib b/Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib rename to Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib b/Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib rename to Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings b/Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings similarity index 100% rename from Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings rename to Externals/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings diff --git a/Sparkle.framework/Versions/A/Sparkle b/Externals/Sparkle.framework/Versions/A/Sparkle similarity index 100% rename from Sparkle.framework/Versions/A/Sparkle rename to Externals/Sparkle.framework/Versions/A/Sparkle diff --git a/Sparkle.framework/Versions/Current b/Externals/Sparkle.framework/Versions/Current similarity index 100% rename from Sparkle.framework/Versions/Current rename to Externals/Sparkle.framework/Versions/Current diff --git a/Externals/google-diff-match-patch/DiffMatchPatch.h b/Externals/google-diff-match-patch/DiffMatchPatch.h new file mode 100755 index 0000000..c70a2bb --- /dev/null +++ b/Externals/google-diff-match-patch/DiffMatchPatch.h @@ -0,0 +1,175 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import + +/* + * Functions for diff, match and patch. + * Computes the difference between two texts to create a patch. + * Applies the patch onto another text, allowing for errors. + */ + +/* + * The data structure representing a diff is an NSMutableArray of Diff objects: + * {Diff(Operation.DIFF_DELETE, "Hello"), + * Diff(Operation.DIFF_INSERT, "Goodbye"), + * Diff(Operation.DIFF_EQUAL, " world.")} + * which means: delete "Hello", add "Goodbye" and keep " world." + */ + +typedef enum { + DIFF_DELETE = 1, + DIFF_INSERT = 2, + DIFF_EQUAL = 3 +} Operation; + + +/* + * Class representing one diff operation. + */ +@interface Diff : NSObject { + Operation operation; // One of: DIFF_INSERT, DIFF_DELETE or DIFF_EQUAL. + NSString *text; // The text associated with this diff operation. +} + +@property (nonatomic, assign) Operation operation; +@property (nonatomic, copy) NSString *text; + ++ (id)diffWithOperation:(Operation)anOperation andText:(NSString *)aText; + +- (id)initWithOperation:(Operation)anOperation andText:(NSString *)aText; + +@end + +/* + * Class representing one patch operation. + */ +@interface Patch : NSObject { + NSMutableArray *diffs; + NSUInteger start1; + NSUInteger start2; + NSUInteger length1; + NSUInteger length2; +} + +@property (nonatomic, retain) NSMutableArray *diffs; +@property (nonatomic, assign) NSUInteger start1; +@property (nonatomic, assign) NSUInteger start2; +@property (nonatomic, assign) NSUInteger length1; +@property (nonatomic, assign) NSUInteger length2; + +@end + + +/* + * Class containing the diff, match and patch methods. + * Also Contains the behaviour settings. + */ +@interface DiffMatchPatch : NSObject { + // Number of seconds to map a diff before giving up (0 for infinity). + NSTimeInterval Diff_Timeout; + + // Cost of an empty edit operation in terms of edit characters. + NSUInteger Diff_EditCost; + + // At what point is no match declared (0.0 = perfection, 1.0 = very loose). + double Match_Threshold; + + // How far to search for a match (0 = exact location, 1000+ = broad match). + // A match this many characters away from the expected location will add + // 1.0 to the score (0.0 is a perfect match). + NSInteger Match_Distance; + + // When deleting a large block of text (over ~64 characters), how close + // do the contents have to be to match the expected contents. (0.0 = + // perfection, 1.0 = very loose). Note that Match_Threshold controls + // how closely the end points of a delete need to match. + float Patch_DeleteThreshold; + + // Chunk size for context length. + uint16_t Patch_Margin; + + // The number of bits in an int. + NSUInteger Match_MaxBits; +} + +@property (nonatomic, assign) NSTimeInterval Diff_Timeout; +@property (nonatomic, assign) NSUInteger Diff_EditCost; +@property (nonatomic, assign) double Match_Threshold; +@property (nonatomic, assign) NSInteger Match_Distance; +@property (nonatomic, assign) float Patch_DeleteThreshold; +@property (nonatomic, assign) uint16_t Patch_Margin; + +- (NSMutableArray *)diff_mainOfOldString:(NSString *)text1 andNewString:(NSString *)text2; +- (NSMutableArray *)diff_mainOfOldString:(NSString *)text1 andNewString:(NSString *)text2 checkLines:(BOOL)checklines; +- (NSUInteger)diff_commonPrefixOfFirstString:(NSString *)text1 andSecondString:(NSString *)text2; +- (NSUInteger)diff_commonSuffixOfFirstString:(NSString *)text1 andSecondString:(NSString *)text2; +- (void)diff_cleanupSemantic:(NSMutableArray *)diffs; +- (void)diff_cleanupSemanticLossless:(NSMutableArray *)diffs; +- (void)diff_cleanupEfficiency:(NSMutableArray *)diffs; +- (void)diff_cleanupMerge:(NSMutableArray *)diffs; +- (NSUInteger)diff_xIndexIn:(NSMutableArray *)diffs location:(NSUInteger) loc; +- (NSString *)diff_prettyHtml:(NSMutableArray *)diffs; +- (NSString *)diff_text1:(NSMutableArray *)diffs; +- (NSString *)diff_text2:(NSMutableArray *)diffs; +- (NSUInteger)diff_levenshtein:(NSMutableArray *)diffs; +- (NSString *)diff_toDelta:(NSMutableArray *)diffs; +- (NSMutableArray *)diff_fromDeltaWithText:(NSString *)text1 andDelta:(NSString *)delta error:(NSError **)error; + +- (NSUInteger)match_mainForText:(NSString *)text pattern:(NSString *)pattern near:(NSUInteger)loc; +- (NSMutableDictionary *)match_alphabet:(NSString *)pattern; + +- (NSMutableArray *)patch_makeFromOldString:(NSString *)text1 andNewString:(NSString *)text2; +- (NSMutableArray *)patch_makeFromDiffs:(NSMutableArray *)diffs; +- (NSMutableArray *)patch_makeFromOldString:(NSString *)text1 newString:(NSString *)text2 diffs:(NSMutableArray *)diffs; +- (NSMutableArray *)patch_makeFromOldString:(NSString *)text1 andDiffs:(NSMutableArray *)diffs; +- (NSMutableArray *)patch_deepCopy:(NSArray *)patches; // Copy rule applies! +- (NSArray *)patch_apply:(NSArray *)sourcePatches toString:(NSString *)text; +- (NSString *)patch_addPadding:(NSMutableArray *)patches; +- (void)patch_splitMax:(NSMutableArray *)patches; +- (NSString *)patch_toText:(NSMutableArray *)patches; +- (NSMutableArray *)patch_fromText:(NSString *)textline error:(NSError **)error; + +@end + + +@interface DiffMatchPatch (PrivateMethods) + +- (NSMutableArray *)diff_mainOfOldString:(NSString *)text1 andNewString:(NSString *)text2 checkLines:(BOOL)checklines deadline:(NSTimeInterval)deadline; +- (NSMutableArray *)diff_computeFromOldString:(NSString *)text1 andNewString:(NSString *)text2 checkLines:(BOOL)checklines deadline:(NSTimeInterval)deadline; +- (NSMutableArray *)diff_lineModeFromOldString:(NSString *)text1 andNewString:(NSString *)text2 deadline:(NSTimeInterval)deadline; +- (NSArray *)diff_linesToCharsForFirstString:(NSString *)text1 andSecondString:(NSString *)text1; +- (NSString *)diff_linesToCharsMungeOfText:(NSString *)text lineArray:(NSMutableArray *)lineArray lineHash:(NSMutableDictionary *)lineHash; +- (void)diff_chars:(NSArray *)diffs toLines:(NSMutableArray *)lineArray; +- (NSMutableArray *)diff_bisectOfOldString:(NSString *)text1 andNewString:(NSString *)text2 deadline:(NSTimeInterval)deadline; +- (NSMutableArray *)diff_bisectSplitOfOldString:(NSString *)text1 andNewString:(NSString *)text2 x:(NSUInteger)x y:(NSUInteger)y deadline:(NSTimeInterval)deadline; +- (NSUInteger)diff_commonOverlapOfFirstString:(NSString *)text1 andSecondString:(NSString *)text2; +- (NSArray *)diff_halfMatchOfFirstString:(NSString *)text1 andSecondString:(NSString *)text2; +- (NSArray *)diff_halfMatchIOfLongString:(NSString *)longtext andShortString:(NSString *)shorttext; +- (NSInteger)diff_cleanupSemanticScoreOfFirstString:(NSString *)one andSecondString:(NSString *)two; + +- (NSUInteger)match_bitapOfText:(NSString *)text andPattern:(NSString *)pattern near:(NSUInteger)loc; +- (double)match_bitapScoreForErrorCount:(NSUInteger)e location:(NSUInteger)x near:(NSUInteger)loc pattern:(NSString *)pattern; + +- (void)patch_addContextToPatch:(Patch *)patch sourceText:(NSString *)text; + +@end diff --git a/Externals/google-diff-match-patch/DiffMatchPatch.m b/Externals/google-diff-match-patch/DiffMatchPatch.m new file mode 100755 index 0000000..15e4045 --- /dev/null +++ b/Externals/google-diff-match-patch/DiffMatchPatch.m @@ -0,0 +1,2560 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import "DiffMatchPatch.h" + +#import "NSString+JavaSubstring.h" +#import "NSString+UriCompatibility.h" +#import "NSMutableDictionary+DMPExtensions.h" +#import "DiffMatchPatchCFUtilities.h" + + +#if !defined(MAX_OF_CONST_AND_DIFF) + // Determines the maximum of two expressions: + // The first is a constant (first parameter) while the second expression is + // the difference between the second and third parameter. The way this is + // calculated prevents integer overflow in the result of the difference. + #define MAX_OF_CONST_AND_DIFF(A,B,C) ((B) <= (C) ? (A) : (B) - (C) + (A)) +#endif + + +// JavaScript-style splice function +void splice(NSMutableArray *input, NSUInteger start, NSUInteger count, NSArray *objects); + +/* NSMutableArray * */ void splice(NSMutableArray *input, NSUInteger start, NSUInteger count, NSArray *objects) { + NSRange deletionRange = NSMakeRange(start, count); + if (objects == nil) { + [input removeObjectsInRange:deletionRange]; + } else { + [input replaceObjectsInRange:deletionRange withObjectsFromArray:objects]; + } +} + +@implementation Diff + +@synthesize operation; +@synthesize text; + +/** + * Constructor. Initializes the diff with the provided values. + * @param operation One of DIFF_INSERT, DIFF_DELETE or DIFF_EQUAL. + * @param text The text being applied. + */ ++ (id)diffWithOperation:(Operation)anOperation + andText:(NSString *)aText; +{ + return [[[self alloc] initWithOperation:anOperation andText:aText] autorelease]; +} + +- (id)initWithOperation:(Operation)anOperation + andText:(NSString *)aText; +{ + self = [super init]; + if (self) { + self.operation = anOperation; + self.text = aText; + } + return self; + +} + +- (void)dealloc +{ + self.text = nil; + + [super dealloc]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + id newDiff = [[[self class] allocWithZone:zone] + initWithOperation:self.operation + andText:self.text]; + + return newDiff; +} + + +/** + * Display a human-readable version of this Diff. + * @return text version. + */ +- (NSString *)description +{ + NSString *prettyText = [self.text stringByReplacingOccurrencesOfString:@"\n" withString:@"\u00b6"]; + NSString *operationName = nil; + switch (self.operation) { + case DIFF_DELETE: + operationName = @"DIFF_DELETE"; + break; + case DIFF_INSERT: + operationName = @"DIFF_INSERT"; + break; + case DIFF_EQUAL: + operationName = @"DIFF_EQUAL"; + break; + default: + break; + } + + return [NSString stringWithFormat:@"Diff(%@,\"%@\")", operationName, prettyText]; +} + +/** + * Is this Diff equivalent to another Diff? + * @param obj Another Diff to compare against. + * @return YES or NO. + */ +- (BOOL)isEqual:(id)obj +{ + // If parameter is nil return NO. + if (obj == nil) { + return NO; + } + + // If parameter cannot be cast to Diff return NO. + if (![obj isKindOfClass:[Diff class]]) { + return NO; + } + + // Return YES if the fields match. + Diff *p = (Diff *)obj; + return p.operation == self.operation && [p.text isEqualToString:self.text]; +} + +- (BOOL)isEqualToDiff:(Diff *)obj +{ + // If parameter is nil return NO. + if (obj == nil) { + return NO; + } + + // Return YES if the fields match. + return obj.operation == self.operation && [obj.text isEqualToString:self.text]; +} + +- (NSUInteger)hash +{ + return ([text hash] ^ (NSUInteger)operation); +} + +@end + + +@implementation Patch + +@synthesize diffs; +@synthesize start1; +@synthesize start2; +@synthesize length1; +@synthesize length2; + +- (id)init +{ + self = [super init]; + + if (self) { + self.diffs = [NSMutableArray array]; + } + + return self; +} + +- (void)dealloc +{ + self.diffs = nil; + + [super dealloc]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + Patch *newPatch = [[[self class] allocWithZone:zone] init]; + + newPatch.diffs = [[NSMutableArray alloc] initWithArray:self.diffs copyItems:YES]; + newPatch.start1 = self.start1; + newPatch.start2 = self.start2; + newPatch.length1 = self.length1; + newPatch.length2 = self.length2; + + return newPatch; +} + + +/** + * Emulate GNU diff's format. + * Header: @@ -382,8 +481,9 @@ + * Indicies are printed as 1-based, not 0-based. + * @return The GNU diff NSString. + */ +- (NSString *)description +{ + NSString *coords1; + NSString *coords2; + + if (self.length1 == 0) { + coords1 = [NSString stringWithFormat:@"%lu,0", + (unsigned long)self.start1]; + } else if (self.length1 == 1) { + coords1 = [NSString stringWithFormat:@"%lu", + (unsigned long)self.start1 + 1]; + } else { + coords1 = [NSString stringWithFormat:@"%lu,%lu", + (unsigned long)self.start1 + 1, (unsigned long)self.length1]; + } + if (self.length2 == 0) { + coords2 = [NSString stringWithFormat:@"%lu,0", + (unsigned long)self.start2]; + } else if (self.length2 == 1) { + coords2 = [NSString stringWithFormat:@"%lu", + (unsigned long)self.start2 + 1]; + } else { + coords2 = [NSString stringWithFormat:@"%lu,%lu", + (unsigned long)self.start2 + 1, (unsigned long)self.length2]; + } + + NSMutableString *text = [NSMutableString stringWithFormat:@"@@ -%@ +%@ @@\n", + coords1, coords2]; + // Escape the body of the patch with %xx notation. + for (Diff *aDiff in self.diffs) { + switch (aDiff.operation) { + case DIFF_INSERT: + [text appendString:@"+"]; + break; + case DIFF_DELETE: + [text appendString:@"-"]; + break; + case DIFF_EQUAL: + [text appendString:@" "]; + break; + } + + [text appendString:[aDiff.text diff_stringByAddingPercentEscapesForEncodeUriCompatibility]]; + [text appendString:@"\n"]; + } + + return text; +} + +@end + + +@implementation DiffMatchPatch + +@synthesize Diff_Timeout; +@synthesize Diff_EditCost; +@synthesize Match_Threshold; +@synthesize Match_Distance; +@synthesize Patch_DeleteThreshold; +@synthesize Patch_Margin; + +- (id)init +{ + self = [super init]; + + if (self) { + Diff_Timeout = 1.0f; + Diff_EditCost = 4; + Match_Threshold = 0.5f; + Match_Distance = 1000; + Patch_DeleteThreshold = 0.5f; + Patch_Margin = 4; + + Match_MaxBits = 32; + } + + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + + +#pragma mark Diff Functions +// DIFF FUNCTIONS + + +/** + * Find the differences between two texts. + * Run a faster, slightly less optimal diff. + * This method allows the 'checklines' of diff_main() to be optional. + * Most of the time checklines is wanted, so default to YES. + * @param text1 Old NSString to be diffed. + * @param text2 New NSString to be diffed. + * @return NSMutableArray of Diff objects. + */ +- (NSMutableArray *)diff_mainOfOldString:(NSString *)text1 + andNewString:(NSString *)text2; +{ + return [self diff_mainOfOldString:text1 andNewString:text2 checkLines:YES]; +} + +/** + * Find the differences between two texts. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If NO, then don't run a + * line-level diff first to identify the changed areas. + * If YES, then run a faster slightly less optimal diff. + * @return NSMutableArray of Diff objects. + */ +- (NSMutableArray *)diff_mainOfOldString:(NSString *)text1 + andNewString:(NSString *)text2 + checkLines:(BOOL)checklines; +{ + // Set a deadline by which time the diff must be complete. + NSTimeInterval deadline; + if (Diff_Timeout <= 0) { + deadline = [[NSDate distantFuture] timeIntervalSinceReferenceDate]; + } else { + deadline = [[NSDate dateWithTimeIntervalSinceNow:Diff_Timeout] timeIntervalSinceReferenceDate]; + } + return [self diff_mainOfOldString:text1 andNewString:text2 checkLines:YES deadline:deadline]; +} + +/** + * Find the differences between two texts. Simplifies the problem by + * stripping any common prefix or suffix off the texts before diffing. + * @param text1 Old NSString to be diffed. + * @param text2 New NSString to be diffed. + * @param checklines Speedup flag. If NO, then don't run a + * line-level diff first to identify the changed areas. + * If YES, then run a faster slightly less optimal diff + * @param deadline Time when the diff should be complete by. Used + * internally for recursive calls. Users should set DiffTimeout + * instead. + * @return NSMutableArray of Diff objects. + */ +- (NSMutableArray *)diff_mainOfOldString:(NSString *)text1 + andNewString:(NSString *)text2 + checkLines:(BOOL)checklines + deadline:(NSTimeInterval)deadline; +{ + // Check for null inputs. + if (text1 == nil || text2 == nil) { + NSLog(@"Null inputs. (diff_main)"); + return nil; + } + + // Check for equality (speedup). + NSMutableArray *diffs; + if ([text1 isEqualToString:text2]) { + diffs = [NSMutableArray array]; + if (text1.length != 0) { + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:text1]]; + } + return diffs; + } + + // Trim off common prefix (speedup). + NSUInteger commonlength = (NSUInteger)diff_commonPrefix((CFStringRef)text1, (CFStringRef)text2); + NSString *commonprefix = [text1 substringWithRange:NSMakeRange(0, commonlength)]; + text1 = [text1 substringFromIndex:commonlength]; + text2 = [text2 substringFromIndex:commonlength]; + + // Trim off common suffix (speedup). + commonlength = (NSUInteger)diff_commonSuffix((CFStringRef)text1, (CFStringRef)text2); + NSString *commonsuffix = [text1 substringFromIndex:text1.length - commonlength]; + text1 = [text1 substringWithRange:NSMakeRange(0, text1.length - commonlength)]; + text2 = [text2 substringWithRange:NSMakeRange(0, text2.length - commonlength)]; + + // Compute the diff on the middle block. + diffs = [self diff_computeFromOldString:text1 andNewString:text2 checkLines:checklines deadline:deadline]; + + // Restore the prefix and suffix. + if (commonprefix.length != 0) { + [diffs insertObject:[Diff diffWithOperation:DIFF_EQUAL andText:commonprefix] atIndex:0]; + } + if (commonsuffix.length != 0) { + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:commonsuffix]]; + } + + [self diff_cleanupMerge:diffs]; + return diffs; +} + +/** + * Determine the common prefix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the start of each string. + */ +- (NSUInteger)diff_commonPrefixOfFirstString:(NSString *)text1 + andSecondString:(NSString *)text2; +{ + return (NSUInteger)diff_commonPrefix((CFStringRef)text1, (CFStringRef)text2); +} + +/** + * Determine the common suffix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of each string. + */ +- (NSUInteger)diff_commonSuffixOfFirstString:(NSString *)text1 + andSecondString:(NSString *)text2; +{ + return (NSUInteger)diff_commonSuffix((CFStringRef)text1, (CFStringRef)text2); +} + +/** + * Determine if the suffix of one CFStringRef is the prefix of another. + * @param text1 First NSString. + * @param text2 Second NSString. + * @return The number of characters common to the end of the first + * CFStringRef and the start of the second CFStringRef. + */ +- (NSUInteger)diff_commonOverlapOfFirstString:(NSString *)text1 + andSecondString:(NSString *)text2; +{ + return (NSUInteger)diff_commonOverlap((CFStringRef)text1, (CFStringRef)text2); +} + +/** + * Do the two texts share a substring which is at least half the length of + * the longer text? + * This speedup can produce non-minimal diffs. + * @param text1 First NSString. + * @param text2 Second NSString. + * @return Five element String array, containing the prefix of text1, the + * suffix of text1, the prefix of text2, the suffix of text2 and the + * common middle. Or NULL if there was no match. + */ +- (NSArray *)diff_halfMatchOfFirstString:(NSString *)text1 + andSecondString:(NSString *)text2; +{ + return [(NSArray *)diff_halfMatchCreate((CFStringRef)text1, (CFStringRef)text2, Diff_Timeout) autorelease]; +} + +/** + * Does a substring of shorttext exist within longtext such that the + * substring is at least half the length of longtext? + * @param longtext Longer NSString. + * @param shorttext Shorter NSString. + * @param i Start index of quarter length substring within longtext. + * @return Five element NSArray, containing the prefix of longtext, the + * suffix of longtext, the prefix of shorttext, the suffix of shorttext + * and the common middle. Or nil if there was no match. + */ +- (NSArray *)diff_halfMatchIOfLongString:(NSString *)longtext + andShortString:(NSString *)shorttext + index:(NSInteger)index; +{ + return [((NSArray *)diff_halfMatchICreate((CFStringRef)longtext, (CFStringRef)shorttext, (CFIndex)index)) autorelease]; +} + +/** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param text1 Old NSString to be diffed. + * @param text2 New NSString to be diffed. + * @param checklines Speedup flag. If NO, then don't run a + * line-level diff first to identify the changed areas. + * If YES, then run a faster slightly less optimal diff. + * @param deadline Time the diff should be complete by. + * @return NSMutableArray of Diff objects. + */ +- (NSMutableArray *)diff_computeFromOldString:(NSString *)text1 + andNewString:(NSString *)text2 + checkLines:(BOOL)checklines + deadline:(NSTimeInterval)deadline; +{ + NSMutableArray *diffs = [NSMutableArray array]; + + if (text1.length == 0) { + // Just add some text (speedup). + [diffs addObject:[Diff diffWithOperation:DIFF_INSERT andText:text2]]; + return diffs; + } + + if (text2.length == 0) { + // Just delete some text (speedup). + [diffs addObject:[Diff diffWithOperation:DIFF_DELETE andText:text1]]; + return diffs; + } + + NSString *longtext = text1.length > text2.length ? text1 : text2; + NSString *shorttext = text1.length > text2.length ? text2 : text1; + NSUInteger i = [longtext rangeOfString:shorttext].location; + if (i != NSNotFound) { + // Shorter text is inside the longer text (speedup). + Operation op = (text1.length > text2.length) ? DIFF_DELETE : DIFF_INSERT; + [diffs addObject:[Diff diffWithOperation:op andText:[longtext substringWithRange:NSMakeRange(0, i)]]]; + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:shorttext]]; + [diffs addObject:[Diff diffWithOperation:op andText:[longtext substringFromIndex:(i + shorttext.length)]]]; + return diffs; + } + + if (shorttext.length == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + [diffs addObject:[Diff diffWithOperation:DIFF_DELETE andText:text1]]; + [diffs addObject:[Diff diffWithOperation:DIFF_INSERT andText:text2]]; + return diffs; + } + + // Check to see if the problem can be split in two. + NSArray *hm = [(NSArray *)diff_halfMatchCreate((CFStringRef)text1, (CFStringRef)text2, Diff_Timeout) autorelease]; + if (hm != nil) { + NSAutoreleasePool *splitPool = [NSAutoreleasePool new]; + // A half-match was found, sort out the return data. + NSString *text1_a = [hm objectAtIndex:0]; + NSString *text1_b = [hm objectAtIndex:1]; + NSString *text2_a = [hm objectAtIndex:2]; + NSString *text2_b = [hm objectAtIndex:3]; + NSString *mid_common = [hm objectAtIndex:4]; + // Send both pairs off for separate processing. + NSMutableArray *diffs_a = [self diff_mainOfOldString:text1_a andNewString:text2_a checkLines:checklines deadline:deadline]; + NSMutableArray *diffs_b = [self diff_mainOfOldString:text1_b andNewString:text2_b checkLines:checklines deadline:deadline]; + // Merge the results. + diffs = [diffs_a retain]; + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:mid_common]]; + [diffs addObjectsFromArray:diffs_b]; + [splitPool drain]; + return [diffs autorelease]; + } + + if (checklines && text1.length > 100 && text2.length > 100) { + return [self diff_lineModeFromOldString:text1 andNewString:text2 deadline:deadline]; + } + + NSAutoreleasePool *bisectPool = [NSAutoreleasePool new]; + diffs = [self diff_bisectOfOldString:text1 andNewString:text2 deadline:deadline]; + [diffs retain]; + [bisectPool drain]; + + return [diffs autorelease]; +} + +/** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param text1 Old NSString to be diffed. + * @param text2 New NSString to be diffed. + * @param deadline Time when the diff should be complete by. + * @return NSMutableArray of Diff objects. + */ +- (NSMutableArray *)diff_lineModeFromOldString:(NSString *)text1 + andNewString:(NSString *)text2 + deadline:(NSTimeInterval)deadline; +{ + // Scan the text on a line-by-line basis first. + NSArray *b = [self diff_linesToCharsForFirstString:text1 andSecondString:text2]; + text1 = (NSString *)[b objectAtIndex:0]; + text2 = (NSString *)[b objectAtIndex:1]; + NSMutableArray *linearray = (NSMutableArray *)[b objectAtIndex:2]; + + NSAutoreleasePool *recursePool = [NSAutoreleasePool new]; + NSMutableArray *diffs = [self diff_mainOfOldString:text1 andNewString:text2 checkLines:NO deadline:deadline]; + [diffs retain]; + [recursePool drain]; + + [diffs autorelease]; + + // Convert the diff back to original text. + [self diff_chars:diffs toLines:linearray]; + // Eliminate freak matches (e.g. blank lines) + [self diff_cleanupSemantic:diffs]; + + // Rediff any Replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:@""]]; + NSUInteger thisPointer = 0; + NSUInteger count_delete = 0; + NSUInteger count_insert = 0; + NSString *text_delete = @""; + NSString *text_insert = @""; + while (thisPointer < diffs.count) { + switch (((Diff *)[diffs objectAtIndex:thisPointer]).operation) { + case DIFF_INSERT: + count_insert++; + text_insert = [text_insert stringByAppendingString:((Diff *)[diffs objectAtIndex:thisPointer]).text]; + break; + case DIFF_DELETE: + count_delete++; + text_delete = [text_delete stringByAppendingString:((Diff *)[diffs objectAtIndex:thisPointer]).text]; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) { + // Delete the offending records and add the merged ones. + NSMutableArray *a = [self diff_mainOfOldString:text_delete andNewString:text_insert checkLines:NO deadline:deadline]; + [diffs removeObjectsInRange:NSMakeRange(thisPointer - count_delete - count_insert, + count_delete + count_insert)]; + thisPointer = thisPointer - count_delete - count_insert; + NSUInteger insertionIndex = thisPointer; + for (Diff *thisDiff in a) { + [diffs insertObject:thisDiff atIndex:insertionIndex]; + insertionIndex++; + } + thisPointer = thisPointer + a.count; + } + count_insert = 0; + count_delete = 0; + text_delete = @""; + text_insert = @""; + break; + } + thisPointer++; + } + [diffs removeLastObject]; // Remove the dummy entry at the end. + + return diffs; +} + +/** + * Split a text into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text NSString to encode. + * @param lineArray NSMutableArray of unique strings. + * @param lineHash Map of strings to indices. + * @return Encoded string. + */ +- (NSString *)diff_linesToCharsMungeOfText:(NSString *)text + lineArray:(NSMutableArray *)lineArray + lineHash:(NSMutableDictionary *)lineHash; +{ + return [((NSString *)diff_linesToCharsMungeCFStringCreate((CFStringRef)text, + (CFMutableArrayRef)lineArray, + (CFMutableDictionaryRef)lineHash)) autorelease]; +} + +/** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time at which to bail if not yet complete. + * @return NSMutableArray of Diff objects. + */ +- (NSMutableArray *)diff_bisectOfOldString:(NSString *)_text1 + andNewString:(NSString *)_text2 + deadline:(NSTimeInterval)deadline; +{ +#define text1CharacterAtIndex(A) text1_chars[(A)] +#define text2CharacterAtIndex(A) text2_chars[(A)] +#define freeTextBuffers() if (text1_buffer != NULL) free(text1_buffer);\ + if (text2_buffer != NULL) free(text2_buffer); + + CFStringRef text1 = (CFStringRef)_text1; + CFStringRef text2 = (CFStringRef)_text2; + + // Cache the text lengths to prevent multiple calls. + CFIndex text1_length = CFStringGetLength(text1); + CFIndex text2_length = CFStringGetLength(text2); + CFIndex max_d = (text1_length + text2_length + 1) / 2; + CFIndex v_offset = max_d; + CFIndex v_length = 2 * max_d; + CFIndex v1[v_length]; + CFIndex v2[v_length]; + for (CFIndex x = 0; x < v_length; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + CFIndex delta = text1_length - text2_length; + + // Prepare access to chars arrays for text1 (massive speedup). + const UniChar *text1_chars; + UniChar *text1_buffer = NULL; + diff_CFStringPrepareUniCharBuffer(text1, &text1_chars, &text1_buffer, CFRangeMake(0, text1_length)); + + // Prepare access to chars arrays for text 2 (massive speedup). + const UniChar *text2_chars; + UniChar *text2_buffer = NULL; + diff_CFStringPrepareUniCharBuffer(text2, &text2_chars, &text2_buffer, CFRangeMake(0, text2_length)); + + // If the total number of characters is odd, then the front path will + // collide with the reverse path. + BOOL front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + CFIndex k1start = 0; + CFIndex k1end = 0; + CFIndex k2start = 0; + CFIndex k2end = 0; + NSMutableArray *diffs; + for (CFIndex d = 0; d < max_d; d++) { + // Bail out if deadline is reached. + if ([NSDate timeIntervalSinceReferenceDate] > deadline) { + break; + } + + // Walk the front path one step. + for (CFIndex k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + CFIndex k1_offset = v_offset + k1; + CFIndex x1; + if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + x1 = v1[k1_offset + 1]; + } else { + x1 = v1[k1_offset - 1] + 1; + } + CFIndex y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length + && text1CharacterAtIndex(x1) == text2CharacterAtIndex(y1)) { + x1++; + y1++; + } + v1[k1_offset] = x1; + if (x1 > text1_length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2_length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + CFIndex k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + // Mirror x2 onto top-left coordinate system. + CFIndex x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) { + freeTextBuffers(); + + // Overlap detected. + return [self diff_bisectSplitOfOldString:_text1 + andNewString:_text2 + x:x1 + y:y1 + deadline:deadline]; + } + } + } + } + + // Walk the reverse path one step. + for (CFIndex k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + CFIndex k2_offset = v_offset + k2; + CFIndex x2; + if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + x2 = v2[k2_offset + 1]; + } else { + x2 = v2[k2_offset - 1] + 1; + } + CFIndex y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length + && text1CharacterAtIndex(text1_length - x2 - 1) + == text2CharacterAtIndex(text2_length - y2 - 1)) { + x2++; + y2++; + } + v2[k2_offset] = x2; + if (x2 > text1_length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2_length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + CFIndex k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + CFIndex x1 = v1[k1_offset]; + CFIndex y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - x2; + if (x1 >= x2) { + // Overlap detected. + freeTextBuffers(); + + return [self diff_bisectSplitOfOldString:_text1 + andNewString:_text2 + x:x1 + y:y1 + deadline:deadline]; + } + } + } + } + } + + freeTextBuffers(); + + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + diffs = [NSMutableArray array]; + [diffs addObject:[Diff diffWithOperation:DIFF_DELETE andText:_text1]]; + [diffs addObject:[Diff diffWithOperation:DIFF_INSERT andText:_text2]]; + return diffs; + +#undef freeTextBuffers +#undef text1CharacterAtIndex +#undef text2CharacterAtIndex +} + +/** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param x Index of split point in text1. + * @param y Index of split point in text2. + * @param deadline Time at which to bail if not yet complete. + * @return NSMutableArray of Diff objects. + */ +- (NSMutableArray *)diff_bisectSplitOfOldString:(NSString *)text1 + andNewString:(NSString *)text2 + x:(NSUInteger)x + y:(NSUInteger)y + deadline:(NSTimeInterval)deadline; +{ + NSString *text1a = [text1 substringToIndex:x]; + NSString *text2a = [text2 substringToIndex:y]; + NSString *text1b = [text1 substringFromIndex:x]; + NSString *text2b = [text2 substringFromIndex:y]; + + // Compute both diffs serially. + NSMutableArray *diffs = [self diff_mainOfOldString:text1a + andNewString:text2a + checkLines:NO + deadline:deadline]; + NSMutableArray *diffsb = [self diff_mainOfOldString:text1b + andNewString:text2b + checkLines:NO + deadline:deadline]; + + [diffs addObjectsFromArray: diffsb]; + return diffs; +} + +/** + * Split two texts into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text1 First NSString. + * @param text2 Second NSString. + * @return Three element NSArray, containing the encoded text1, the + * encoded text2 and the NSMutableArray of unique strings. The zeroth element + * of the NSArray of unique strings is intentionally blank. + */ +- (NSArray *)diff_linesToCharsForFirstString:(NSString *)text1 + andSecondString:(NSString *)text2; +{ + NSMutableArray *lineArray = [NSMutableArray array]; // NSString objects + NSMutableDictionary *lineHash = [NSMutableDictionary dictionary]; // keys: NSString, values:NSNumber + // e.g. [lineArray objectAtIndex:4] == "Hello\n" + // e.g. [lineHash objectForKey:"Hello\n"] == 4 + + // "\x00" is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a nil character. + [lineArray addObject:@""]; + + NSString *chars1 = (NSString *)diff_linesToCharsMungeCFStringCreate((CFStringRef)text1, + (CFMutableArrayRef)lineArray, + (CFMutableDictionaryRef)lineHash); + NSString *chars2 = (NSString *)diff_linesToCharsMungeCFStringCreate((CFStringRef)text2, + (CFMutableArrayRef)lineArray, + (CFMutableDictionaryRef)lineHash); + + NSArray *result = [NSArray arrayWithObjects:chars1, chars2, lineArray, nil]; + + [chars1 release]; + [chars2 release]; + + return result; +} + +/** + * Rehydrate the text in a diff from an NSString of line hashes to real lines + * of text. + * @param NSArray of Diff objects. + * @param NSMutableArray of unique strings. + */ +- (void)diff_chars:(NSArray *)diffs toLines:(NSMutableArray *)lineArray; +{ + NSMutableString *text; + NSUInteger lineHash; + for (Diff *diff in diffs) { + text = [NSMutableString string]; + for (NSUInteger y = 0; y < [diff.text length]; y++) { + lineHash = (NSUInteger)[diff.text characterAtIndex:y]; + [text appendString:[lineArray objectAtIndex:lineHash]]; + } + diff.text = text; + } +} + +/** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param diffs NSMutableArray of Diff objects. + */ +- (void)diff_cleanupMerge:(NSMutableArray *)diffs; +{ +#define prevDiff ((Diff *)[diffs objectAtIndex:(thisPointer - 1)]) +#define thisDiff ((Diff *)[diffs objectAtIndex:thisPointer]) +#define nextDiff ((Diff *)[diffs objectAtIndex:(thisPointer + 1)]) + + if (diffs.count == 0) { + return; + } + + // Add a dummy entry at the end. + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:@""]]; + NSUInteger thisPointer = 0; + NSUInteger count_delete = 0; + NSUInteger count_insert = 0; + NSString *text_delete = @""; + NSString *text_insert = @""; + NSUInteger commonlength; + while (thisPointer < diffs.count) { + switch (thisDiff.operation) { + case DIFF_INSERT: + count_insert++; + text_insert = [text_insert stringByAppendingString:thisDiff.text]; + thisPointer++; + break; + case DIFF_DELETE: + count_delete++; + text_delete = [text_delete stringByAppendingString:thisDiff.text]; + thisPointer++; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete + count_insert > 1) { + if (count_delete != 0 && count_insert != 0) { + // Factor out any common prefixes. + commonlength = (NSUInteger)diff_commonPrefix((CFStringRef)text_insert, (CFStringRef)text_delete); + if (commonlength != 0) { + if ((thisPointer - count_delete - count_insert) > 0 && + ((Diff *)[diffs objectAtIndex:(thisPointer - count_delete - count_insert - 1)]).operation + == DIFF_EQUAL) { + ((Diff *)[diffs objectAtIndex:(thisPointer - count_delete - count_insert - 1)]).text + = [((Diff *)[diffs objectAtIndex:(thisPointer - count_delete - count_insert - 1)]).text + stringByAppendingString:[text_insert substringWithRange:NSMakeRange(0, commonlength)]]; + } else { + [diffs insertObject:[Diff diffWithOperation:DIFF_EQUAL + andText:[text_insert substringWithRange:NSMakeRange(0, commonlength)]] + atIndex:0]; + thisPointer++; + } + text_insert = [text_insert substringFromIndex:commonlength]; + text_delete = [text_delete substringFromIndex:commonlength]; + } + // Factor out any common suffixes. + commonlength = (NSUInteger)diff_commonSuffix((CFStringRef)text_insert, (CFStringRef)text_delete); + if (commonlength != 0) { + thisDiff.text = [[text_insert substringFromIndex:(text_insert.length + - commonlength)] stringByAppendingString:thisDiff.text]; + text_insert = [text_insert substringWithRange:NSMakeRange(0, + text_insert.length - commonlength)]; + text_delete = [text_delete substringWithRange:NSMakeRange(0, + text_delete.length - commonlength)]; + } + } + // Delete the offending records and add the merged ones. + if (count_delete == 0) { + splice(diffs, thisPointer - count_insert, + count_delete + count_insert, + [NSMutableArray arrayWithObject:[Diff diffWithOperation:DIFF_INSERT andText:text_insert]]); + } else if (count_insert == 0) { + splice(diffs, thisPointer - count_delete, + count_delete + count_insert, + [NSMutableArray arrayWithObject:[Diff diffWithOperation:DIFF_DELETE andText:text_delete]]); + } else { + splice(diffs, thisPointer - count_delete - count_insert, + count_delete + count_insert, + [NSMutableArray arrayWithObjects:[Diff diffWithOperation:DIFF_DELETE andText:text_delete], + [Diff diffWithOperation:DIFF_INSERT andText:text_insert], nil]); + } + thisPointer = thisPointer - count_delete - count_insert + + (count_delete != 0 ? 1 : 0) + (count_insert != 0 ? 1 : 0) + 1; + } else if (thisPointer != 0 && prevDiff.operation == DIFF_EQUAL) { + // Merge this equality with the previous one. + prevDiff.text = [prevDiff.text stringByAppendingString:thisDiff.text]; + [diffs removeObjectAtIndex:thisPointer]; + } else { + thisPointer++; + } + count_insert = 0; + count_delete = 0; + text_delete = @""; + text_insert = @""; + break; + } + } + if (((Diff *)diffs.lastObject).text.length == 0) { + [diffs removeLastObject]; // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by + // equalities which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + BOOL changes = NO; + thisPointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (thisPointer < (diffs.count - 1)) { + if (prevDiff.operation == DIFF_EQUAL && + nextDiff.operation == DIFF_EQUAL) { + // This is a single edit surrounded by equalities. + if ([thisDiff.text hasSuffix:prevDiff.text]) { + // Shift the edit over the previous equality. + thisDiff.text = [prevDiff.text stringByAppendingString: + [thisDiff.text substringWithRange:NSMakeRange(0, thisDiff.text.length - prevDiff.text.length)]]; + nextDiff.text = [prevDiff.text stringByAppendingString:nextDiff.text]; + splice(diffs, thisPointer - 1, 1, nil); + changes = YES; + } else if ([thisDiff.text hasPrefix:nextDiff.text]) { + // Shift the edit over the next equality. + prevDiff.text = [prevDiff.text stringByAppendingString:nextDiff.text]; + thisDiff.text = [[thisDiff.text substringFromIndex:nextDiff.text.length] stringByAppendingString:nextDiff.text]; + splice(diffs, thisPointer + 1, 1, nil); + changes = YES; + } + } + thisPointer++; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + [self diff_cleanupMerge:diffs]; + } + +#undef prevDiff +#undef thisDiff +#undef nextDiff +} + + +/** + * Look for single edits surrounded on both sides by equalities + * which can be shifted sideways to align the edit to a word boundary. + * e.g: The cat came. -> The cat came. + * @param diffs NSMutableArray of Diff objects. + */ +- (void)diff_cleanupSemanticLossless:(NSMutableArray *)diffs; +{ +#define prevDiff ((Diff *)[diffs objectAtIndex:(thisPointer - 1)]) +#define thisDiff ((Diff *)[diffs objectAtIndex:thisPointer]) +#define nextDiff ((Diff *)[diffs objectAtIndex:(thisPointer + 1)]) + + if (diffs.count == 0) { + return; + } + + NSUInteger thisPointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (thisPointer < (diffs.count - 1)) { + if (prevDiff.operation == DIFF_EQUAL && nextDiff.operation == DIFF_EQUAL) { + // This is a single edit surrounded by equalities. + NSString *equality1 = prevDiff.text; + NSString *edit = thisDiff.text; + NSString *equality2 = nextDiff.text; + + // First, shift the edit as far left as possible. + NSUInteger commonOffset = (NSUInteger)diff_commonSuffix((CFStringRef)equality1, (CFStringRef)edit); + + if (commonOffset > 0) { + NSString *commonString = [edit substringFromIndex:(edit.length - commonOffset)]; + equality1 = [equality1 substringWithRange:NSMakeRange(0, (equality1.length - commonOffset))]; + edit = [commonString stringByAppendingString:[edit substringWithRange:NSMakeRange(0, (edit.length - commonOffset))]]; + equality2 = [commonString stringByAppendingString:equality2]; + } + + // Second, step right character by character, + // looking for the best fit. + NSString *bestEquality1 = equality1; + NSString *bestEdit = edit; + NSString *bestEquality2 = equality2; + CFIndex bestScore = diff_cleanupSemanticScore((CFStringRef)equality1, (CFStringRef)edit) + + diff_cleanupSemanticScore((CFStringRef)edit, (CFStringRef)equality2); + while (edit.length != 0 && equality2.length != 0 + && [edit characterAtIndex:0] == [equality2 characterAtIndex:0]) { + equality1 = [equality1 stringByAppendingString:[edit substringWithRange:NSMakeRange(0, 1)]]; + edit = [[edit substringFromIndex:1] stringByAppendingString:[equality2 substringWithRange:NSMakeRange(0, 1)]]; + equality2 = [equality2 substringFromIndex:1]; + CFIndex score = diff_cleanupSemanticScore((CFStringRef)equality1, (CFStringRef)edit) + + diff_cleanupSemanticScore((CFStringRef)edit, (CFStringRef)equality2); + // The >= encourages trailing rather than leading whitespace on edits. + if (score >= bestScore) { + bestScore = score; + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + } + } + + if (prevDiff.text != bestEquality1) { + // We have an improvement, save it back to the diff. + if (bestEquality1.length != 0) { + prevDiff.text = bestEquality1; + } else { + [diffs removeObjectAtIndex:thisPointer - 1]; + thisPointer--; + } + thisDiff.text = bestEdit; + if (bestEquality2.length != 0) { + nextDiff.text = bestEquality2; + } else { + [diffs removeObjectAtIndex:thisPointer + 1]; + thisPointer--; + } + } + } + thisPointer++; + } + +#undef prevDiff +#undef thisDiff +#undef nextDiff +} + +/** + * Given two strings, comAdde a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 5 (best) to 0 (worst). + * @param one First string. + * @param two Second string. + * @return The score. + */ +- (NSInteger)diff_cleanupSemanticScoreOfFirstString:(NSString *)one + andSecondString:(NSString *)two; +{ + return diff_cleanupSemanticScore((CFStringRef)one, (CFStringRef)two); +} + +/** + * Reduce the number of edits by eliminating operationally trivial + * equalities. + * @param diffs NSMutableArray of Diff objects. + */ +- (void)diff_cleanupEfficiency:(NSMutableArray *)diffs; +{ +#define thisDiff ((Diff *)[diffs objectAtIndex:thisPointer]) +#define equalitiesLastItem ((NSNumber *)equalities.lastObject) +#define equalitiesLastValue ((NSNumber *)equalities.lastObject).integerValue + if (diffs.count == 0) { + return; + } + + BOOL changes = NO; + // Stack of indices where equalities are found. + NSMutableArray *equalities = [NSMutableArray array]; + // Always equal to equalities.lastObject.text + NSString *lastequality = nil; + NSInteger thisPointer = 0; // Index of current position. + // Is there an insertion operation before the last equality. + BOOL pre_ins = NO; + // Is there a deletion operation before the last equality. + BOOL pre_del = NO; + // Is there an insertion operation after the last equality. + BOOL post_ins = NO; + // Is there a deletion operation after the last equality. + BOOL post_del = NO; + + NSUInteger indexToChange; + Diff *diffToChange; + + while (thisPointer < (NSInteger)diffs.count) { + if (thisDiff.operation == DIFF_EQUAL) { // Equality found. + if (thisDiff.text.length < Diff_EditCost && (post_ins || post_del)) { + // Candidate found. + [equalities addObject:[NSNumber numberWithInteger:thisPointer]]; + pre_ins = post_ins; + pre_del = post_del; + lastequality = thisDiff.text; + } else { + // Not a candidate, and can never become one. + [equalities removeAllObjects]; + lastequality = nil; + } + post_ins = post_del = NO; + } else { // An insertion or deletion. + if (thisDiff.operation == DIFF_DELETE) { + post_del = YES; + } else { + post_ins = YES; + } + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if (lastequality != nil + && ((pre_ins && pre_del && post_ins && post_del) + || ((lastequality.length < Diff_EditCost / 2) + && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + (post_ins ? 1 : 0) + + (post_del ? 1 : 0)) == 3))) { + // Duplicate record. + [diffs insertObject:[Diff diffWithOperation:DIFF_DELETE andText:lastequality] + atIndex:equalitiesLastValue]; + // Change second copy to insert. + // Hash values for objects must not change while in a collection + indexToChange = equalitiesLastValue + 1; + diffToChange = [[diffs objectAtIndex:indexToChange] retain]; + [diffs replaceObjectAtIndex:indexToChange withObject:[NSNull null]]; + diffToChange.operation = DIFF_INSERT; + [diffs replaceObjectAtIndex:indexToChange withObject:diffToChange]; + [diffToChange release]; + + [equalities removeLastObject]; // Throw away the equality we just deleted. + lastequality = nil; + if (pre_ins && pre_del) { + // No changes made which could affect previous entry, keep going. + post_ins = post_del = YES; + [equalities removeAllObjects]; + } else { + if (equalities.count > 0) { + [equalities removeLastObject]; + } + + thisPointer = equalities.count > 0 ? equalitiesLastValue : -1; + post_ins = post_del = NO; + } + changes = YES; + } + } + thisPointer++; + } + + if (changes) { + [self diff_cleanupMerge:diffs]; + } + +#undef thisDiff +#undef equalitiesLastItem +#undef equalitiesLastValue +} + +/** + * Convert a Diff list into a pretty HTML report. + * @param diffs NSMutableArray of Diff objects. + * @return HTML representation. + */ +- (NSString *)diff_prettyHtml:(NSMutableArray *)diffs; +{ + NSMutableString *html = [NSMutableString string]; + for (Diff *aDiff in diffs) { + NSMutableString *text = [[aDiff.text mutableCopy] autorelease]; + [text replaceOccurrencesOfString:@"&" withString:@"&" options:NSLiteralSearch range:NSMakeRange(0, text.length)]; + [text replaceOccurrencesOfString:@"<" withString:@"<" options:NSLiteralSearch range:NSMakeRange(0, text.length)]; + [text replaceOccurrencesOfString:@">" withString:@">" options:NSLiteralSearch range:NSMakeRange(0, text.length)]; + [text replaceOccurrencesOfString:@"\n" withString:@"¶
" options:NSLiteralSearch range:NSMakeRange(0, text.length)]; + + switch (aDiff.operation) { + case DIFF_INSERT: + [html appendFormat:@"%@", text]; + break; + case DIFF_DELETE: + [html appendFormat:@"%@", text]; + break; + case DIFF_EQUAL: + [html appendFormat:@"%@", text]; + break; + } + } + return html; +} + +/** + * Compute and return the source text (all equalities and deletions). + * @param diffs NSMutableArray of Diff objects. + * @return Source text. + */ +- (NSString *)diff_text1:(NSMutableArray *)diffs; +{ + NSMutableString *text = [NSMutableString string]; + for (Diff *aDiff in diffs) { + if (aDiff.operation != DIFF_INSERT) { + [text appendString:aDiff.text]; + } + } + return text; +} + +/** + * Compute and return the destination text (all equalities and insertions). + * @param diffs NSMutableArray of Diff objects. + * @return Destination text. + */ +- (NSString *)diff_text2:(NSMutableArray *)diffs; +{ + NSMutableString *text = [NSMutableString string]; + for (Diff *aDiff in diffs) { + if (aDiff.operation != DIFF_DELETE) { + [text appendString:aDiff.text]; + } + } + return text; +} + +/** + * Crush the diff into an encoded NSString which describes the operations + * required to transform text1 into text2. + * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. + * Operations are tab-separated. Inserted text is escaped using %xx + * notation. + * @param diffs NSMutableArray of Diff objects. + * @return Delta text. + */ +- (NSString *)diff_toDelta:(NSMutableArray *)diffs; +{ + NSMutableString *delta = [NSMutableString string]; + for (Diff *aDiff in diffs) { + switch (aDiff.operation) { + case DIFF_INSERT: + [delta appendFormat:@"+%@\t", [[aDiff.text diff_stringByAddingPercentEscapesForEncodeUriCompatibility] + stringByReplacingOccurrencesOfString:@"%20" withString:@" "]]; + break; + case DIFF_DELETE: + [delta appendFormat:@"-%" PRId32 "\t", (int32_t)aDiff.text.length]; + break; + case DIFF_EQUAL: + [delta appendFormat:@"=%" PRId32 "\t", (int32_t)aDiff.text.length]; + break; + } + } + + if (delta.length != 0) { + // Strip off trailing tab character. + return [delta substringWithRange:NSMakeRange(0, delta.length-1)]; + } + return delta; +} + +/** + * Given the original text1, and an encoded NSString which describes the + * operations required to transform text1 into text2, compute the full diff. + * @param text1 Source NSString for the diff. + * @param delta Delta text. + * @param error NSError if invalid input. + * @return NSMutableArray of Diff objects or nil if invalid. + */ +- (NSMutableArray *)diff_fromDeltaWithText:(NSString *)text1 + andDelta:(NSString *)delta + error:(NSError **)error; +{ + NSMutableArray *diffs = [NSMutableArray array]; + NSUInteger thisPointer = 0; // Cursor in text1 + NSArray *tokens = [delta componentsSeparatedByString:@"\t"]; + NSInteger n; + NSDictionary *errorDetail = nil; + for (NSString *token in tokens) { + if (token.length == 0) { + // Blank tokens are ok (from a trailing \t). + continue; + } + // Each token begins with a one character parameter which specifies the + // operation of this token (delete, insert, equality). + NSString *param = [token substringFromIndex:1]; + switch ([token characterAtIndex:0]) { + case '+': + param = [param diff_stringByReplacingPercentEscapesForEncodeUriCompatibility]; + if (param == nil) { + if (error != NULL) { + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Invalid character in diff_fromDelta: %@", @"Error"), param], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:99 userInfo:errorDetail]; + } + return nil; + } + [diffs addObject:[Diff diffWithOperation:DIFF_INSERT andText:param]]; + break; + case '-': + // Fall through. + case '=': + n = [param integerValue]; + if (n == 0) { + if (error != NULL) { + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Invalid number in diff_fromDelta: %@", @"Error"), param], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:100 userInfo:errorDetail]; + } + return nil; + } else if (n < 0) { + if (error != NULL) { + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Negative number in diff_fromDelta: %@", @"Error"), param], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:101 userInfo:errorDetail]; + } + return nil; + } + NSString *text; + @try { + text = [text1 substringWithRange:NSMakeRange(thisPointer, (NSUInteger)n)]; + thisPointer += (NSUInteger)n; + } + @catch (NSException *e) { + if (error != NULL) { + // CHANGME: Pass on the information contained in e + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Delta length (%lu) larger than source text length (%lu).", @"Error"), + (unsigned long)thisPointer, (unsigned long)text1.length], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:102 userInfo:errorDetail]; + } + return nil; + } + if ([token characterAtIndex:0] == '=') { + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:text]]; + } else { + [diffs addObject:[Diff diffWithOperation:DIFF_DELETE andText:text]]; + } + break; + default: + // Anything else is an error. + if (error != NULL) { + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Invalid diff operation in diff_fromDelta: %C", @"Error"), + [token characterAtIndex:0]], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:102 userInfo:errorDetail]; + } + return nil; + } + } + if (thisPointer != text1.length) { + if (error != NULL) { + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Delta length (%lu) smaller than source text length (%lu).", @"Error"), + (unsigned long)thisPointer, (unsigned long)text1.length], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:103 userInfo:errorDetail]; + } + return nil; + } + return diffs; +} + +/** + * loc is a location in text1, compute and return the equivalent location in + * text2. + * e.g. "The cat" vs "The big cat", 1->1, 5->8 + * @param diffs NSMutableArray of Diff objects. + * @param loc Location within text1. + * @return Location within text2. + */ +- (NSUInteger)diff_xIndexIn:(NSMutableArray *)diffs + location:(NSUInteger) loc; +{ + NSUInteger chars1 = 0; + NSUInteger chars2 = 0; + NSUInteger last_chars1 = 0; + NSUInteger last_chars2 = 0; + Diff *lastDiff = nil; + for (Diff *aDiff in diffs) { + if (aDiff.operation != DIFF_INSERT) { + // Equality or deletion. + chars1 += aDiff.text.length; + } + if (aDiff.operation != DIFF_DELETE) { + // Equality or insertion. + chars2 += aDiff.text.length; + } + if (chars1 > loc) { + // Overshot the location. + lastDiff = aDiff; + break; + } + last_chars1 = chars1; + last_chars2 = chars2; + } + if (lastDiff != nil && lastDiff.operation == DIFF_DELETE) { + // The location was deleted. + return last_chars2; + } + // Add the remaining character length. + return last_chars2 + (loc - last_chars1); +} + +/** + * Compute the Levenshtein distance; the number of inserted, deleted or + * substituted characters. + * @param diffs NSMutableArray of Diff objects. + * @return Number of changes. + */ +- (NSUInteger)diff_levenshtein:(NSMutableArray *)diffs; +{ + NSUInteger levenshtein = 0; + NSUInteger insertions = 0; + NSUInteger deletions = 0; + for (Diff *aDiff in diffs) { + switch (aDiff.operation) { + case DIFF_INSERT: + insertions += aDiff.text.length; + break; + case DIFF_DELETE: + deletions += aDiff.text.length; + break; + case DIFF_EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += MAX(insertions, deletions); + insertions = 0; + deletions = 0; + break; + } + } + levenshtein += MAX(insertions, deletions); + return levenshtein; +} + +/** + * Reduce the number of edits by eliminating semantically trivial + * equalities. + * @param diffs NSMutableArray of Diff objects. + */ +- (void)diff_cleanupSemantic:(NSMutableArray *)diffs; +{ +#define prevDiff ((Diff *)[diffs objectAtIndex:(thisPointer - 1)]) +#define thisDiff ((Diff *)[diffs objectAtIndex:thisPointer]) +#define nextDiff ((Diff *)[diffs objectAtIndex:(thisPointer + 1)]) +#define equalitiesLastItem ((NSNumber *)equalities.lastObject) +#define equalitiesLastValue ((NSNumber *)equalities.lastObject).integerValue + + if (diffs == nil || diffs.count == 0) { + return; + } + + BOOL changes = NO; + // Stack of indices where equalities are found. + NSMutableArray *equalities = [NSMutableArray array]; + // Always equal to equalities.lastObject.text + NSString *lastequality = nil; + NSUInteger thisPointer = 0; // Index of current position. + // Number of characters that changed prior to the equality. + NSUInteger length_insertions1 = 0; + NSUInteger length_deletions1 = 0; + // Number of characters that changed after the equality. + NSUInteger length_insertions2 = 0; + NSUInteger length_deletions2 = 0; + + NSUInteger indexToChange; + Diff *diffToChange; + + while (thisPointer < diffs.count) { + if (thisDiff.operation == DIFF_EQUAL) { // Equality found. + [equalities addObject:[NSNumber numberWithInteger:thisPointer]]; + length_insertions1 = length_insertions2; + length_deletions1 = length_deletions2; + length_insertions2 = 0; + length_deletions2 = 0; + lastequality = thisDiff.text; + } else { // an insertion or deletion + if (thisDiff.operation == DIFF_INSERT) { + length_insertions2 += thisDiff.text.length; + } else { + length_deletions2 += thisDiff.text.length; + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (lastequality != nil + && (lastequality.length <= MAX(length_insertions1, length_deletions1)) + && (lastequality.length <= MAX(length_insertions2, length_deletions2))) { + // Duplicate record. + [diffs insertObject:[Diff diffWithOperation:DIFF_DELETE andText:lastequality] atIndex:equalitiesLastValue]; + // Change second copy to insert. + // Hash values for objects must not change while in a collection. + indexToChange = equalitiesLastValue + 1; + diffToChange = [[diffs objectAtIndex:indexToChange] retain]; + [diffs replaceObjectAtIndex:indexToChange withObject:[NSNull null]]; + diffToChange.operation = DIFF_INSERT; + [diffs replaceObjectAtIndex:indexToChange withObject:diffToChange]; + [diffToChange release]; + + // Throw away the equality we just deleted. + [equalities removeLastObject]; + if (equalities.count > 0) { + [equalities removeLastObject]; + } + // Setting an unsigned value to -1 may seem weird to some, + // but we will pass thru a ++ below: + // => overflow => 0 + thisPointer = equalities.count > 0 ? equalitiesLastValue : -1; + length_insertions1 = 0; // Reset the counters. + length_deletions1 = 0; + length_insertions2 = 0; + length_deletions2 = 0; + lastequality = nil; + changes = YES; + } + } + thisPointer++; + } + + // Normalize the diff. + if (changes) { + [self diff_cleanupMerge:diffs]; + } + [self diff_cleanupSemanticLossless:diffs]; + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + thisPointer = 1; + while (thisPointer < diffs.count) { + if (prevDiff.operation == DIFF_DELETE && thisDiff.operation == DIFF_INSERT) { + NSString *deletion = prevDiff.text; + NSString *insertion = thisDiff.text; + NSUInteger overlap_length1 = (NSUInteger)diff_commonOverlap((CFStringRef)deletion, (CFStringRef)insertion); + NSUInteger overlap_length2 = (NSUInteger)diff_commonOverlap((CFStringRef)insertion, (CFStringRef)deletion); + if (overlap_length1 >= overlap_length2) { + if (overlap_length1 >= deletion.length / 2.0 || + overlap_length1 >= insertion.length / 2.0) { + // Overlap found. + // Insert an equality and trim the surrounding edits. + [diffs insertObject:[Diff diffWithOperation:DIFF_EQUAL + andText:[insertion substringWithRange:NSMakeRange(0, overlap_length1)]] + atIndex:thisPointer]; + prevDiff.text = [deletion substringWithRange:NSMakeRange(0, deletion.length - overlap_length1)]; + nextDiff.text = [insertion substringFromIndex:overlap_length1]; + thisPointer++; + } + } else { + if (overlap_length2 >= deletion.length / 2.0 || + overlap_length2 >= insertion.length / 2.0) { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + [diffs insertObject:[Diff diffWithOperation:DIFF_EQUAL + andText:[deletion substringWithRange:NSMakeRange(0, overlap_length2)]] + atIndex:thisPointer]; + prevDiff.operation = DIFF_INSERT; + prevDiff.text = [insertion substringWithRange:NSMakeRange(0, insertion.length - overlap_length2)]; + nextDiff.operation = DIFF_DELETE; + nextDiff.text = [deletion substringFromIndex:overlap_length2]; + thisPointer++; + } + } + thisPointer++; + } + thisPointer++; + } + +#undef prevDiff +#undef thisDiff +#undef nextDiff +#undef equalitiesLastItem +#undef equalitiesLastValue +} + +#pragma mark Match Functions +// MATCH FUNCTIONS + + +/** + * Locate the best instance of 'pattern' in 'text' near 'loc'. + * Returns NSNotFound if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or NSNotFound. + */ +- (NSUInteger)match_mainForText:(NSString *)text + pattern:(NSString *)pattern + near:(NSUInteger)loc; +{ + // Check for null inputs. + if (text == nil || pattern == nil) { + NSLog(@"Null inputs. (match_main)"); + return NSNotFound; + } + if (text.length == 0) { + NSLog(@"Empty text. (match_main)"); + return NSNotFound; + } + + NSUInteger new_loc; + new_loc = MIN(loc, text.length); + new_loc = MAX((NSUInteger)0, new_loc); + + if ([text isEqualToString:pattern]) { + // Shortcut (potentially not guaranteed by the algorithm) + return 0; + } else if (text.length == 0) { + // Nothing to match. + return NSNotFound; + } else if (new_loc + pattern.length <= text.length + && [[text substringWithRange:NSMakeRange(new_loc, pattern.length)] isEqualToString:pattern]) { + // Perfect match at the perfect spot! (Includes case of empty pattern) + return new_loc; + } else { + // Do a fuzzy compare. + return [self match_bitapOfText:text andPattern:pattern near:new_loc]; + } +} + +/** + * Locate the best instance of 'pattern' in 'text' near 'loc' using the + * Bitap algorithm. Returns NSNotFound if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or NSNotFound. + */ +- (NSUInteger)match_bitapOfText:(NSString *)text + andPattern:(NSString *)pattern + near:(NSUInteger)loc; +{ + NSAssert((Match_MaxBits == 0 || pattern.length <= Match_MaxBits), + @"Pattern too long for this application."); + + // Initialise the alphabet. + NSMutableDictionary *s = [self match_alphabet:pattern]; + + // Highest score beyond which we give up. + double score_threshold = Match_Threshold; + // Is there a nearby exact match? (speedup) + NSUInteger best_loc = [text rangeOfString:pattern options:NSLiteralSearch range:NSMakeRange(loc, text.length - loc)].location; + if (best_loc != NSNotFound) { + score_threshold = MIN([self match_bitapScoreForErrorCount:0 location:best_loc near:loc pattern:pattern], score_threshold); + // What about in the other direction? (speedup) + NSUInteger searchRangeLoc = MIN(loc + pattern.length, text.length); + NSRange searchRange = NSMakeRange(0, searchRangeLoc); + best_loc = [text rangeOfString:pattern options:(NSLiteralSearch | NSBackwardsSearch) range:searchRange].location; + if (best_loc != NSNotFound) { + score_threshold = MIN([self match_bitapScoreForErrorCount:0 location:best_loc near:loc pattern:pattern], score_threshold); + } + } + + // Initialise the bit arrays. + NSUInteger matchmask = 1 << (pattern.length - 1); + best_loc = NSNotFound; + + NSUInteger bin_min, bin_mid; + NSUInteger bin_max = pattern.length + text.length; + NSUInteger *rd = NULL; + NSUInteger *last_rd = NULL; + for (NSUInteger d = 0; d < pattern.length; d++) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at + // this error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) { + double score = [self match_bitapScoreForErrorCount:d location:(loc + bin_mid) near:loc pattern:pattern]; + if (score <= score_threshold) { + bin_min = bin_mid; + } else { + bin_max = bin_mid; + } + bin_mid = (bin_max - bin_min) / 2 + bin_min; + } + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + NSUInteger start = MAX_OF_CONST_AND_DIFF(1, loc, bin_mid); + NSUInteger finish = MIN(loc + bin_mid, text.length) + pattern.length; + + rd = (NSUInteger *)calloc((finish + 2), sizeof(NSUInteger)); + rd[finish + 1] = (1 << d) - 1; + + for (NSUInteger j = finish; j >= start; j--) { + NSUInteger charMatch; + if (text.length <= j - 1 || ![s diff_containsObjectForUnicharKey:[text characterAtIndex:(j - 1)]]) { + // Out of range. + charMatch = 0; + } else { + charMatch = [s diff_unsignedIntegerForUnicharKey:[text characterAtIndex:(j - 1)]]; + } + if (d == 0) { + // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } else { + // Subsequent passes: fuzzy match. + rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; + } + if ((rd[j] & matchmask) != 0) { + double score = [self match_bitapScoreForErrorCount:d location:(j - 1) near:loc pattern:pattern]; + // This match will almost certainly be better than any existing match. + // But check anyway. + if (score <= score_threshold) { + // Told you so. + score_threshold = score; + best_loc = j - 1; + if (best_loc > loc) { + // When passing loc, don't exceed our current distance from loc. + start = MAX_OF_CONST_AND_DIFF(1, 2 * loc, best_loc); + } else { + // Already passed loc, downhill from here on in. + break; + } + } + } + } + if ([self match_bitapScoreForErrorCount:(d + 1) location:loc near:loc pattern:pattern] > score_threshold) { + // No hope for a (better) match at greater error levels. + break; + } + + if (last_rd != NULL) { + free(last_rd); + } + last_rd = rd; + } + + if (rd != NULL && last_rd != rd) { + free(rd); + } + if (last_rd != NULL) { + free(last_rd); + } + + return best_loc; +} + +/** + * Compute and return the score for a match with e errors and x location. + * @param e Number of errors in match. + * @param x Location of match. + * @param loc Expected location of match. + * @param pattern Pattern being sought. + * @return Overall score for match (0.0 = good, 1.0 = bad). + */ +- (double)match_bitapScoreForErrorCount:(NSUInteger)e + location:(NSUInteger)x + near:(NSUInteger)loc + pattern:(NSString *)pattern; +{ + double score; + + double accuracy = (double)e / pattern.length; + NSUInteger proximity = (NSUInteger)ABS((long long)loc - (long long)x); + if (Match_Distance == 0) { + // Dodge divide by zero error. + return proximity == 0 ? accuracy : 1.0; + } + score = accuracy + (proximity / (double) Match_Distance); + + return score; +} + +/** + * Initialise the alphabet for the Bitap algorithm. + * @param pattern The text to encode. + * @return Hash of character locations + * (NSMutableDictionary: keys:NSString/unichar, values:NSNumber/NSUInteger). + */ +- (NSMutableDictionary *)match_alphabet:(NSString *)pattern; +{ + NSMutableDictionary *s = [NSMutableDictionary dictionary]; + CFStringRef str = (CFStringRef)pattern; + CFStringInlineBuffer inlineBuffer; + CFIndex length; + CFIndex cnt; + + length = CFStringGetLength(str); + CFStringInitInlineBuffer(str, &inlineBuffer, CFRangeMake(0, length)); + + UniChar ch; + CFStringRef c; + for (cnt = 0; cnt < length; cnt++) { + ch = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, cnt); + c = diff_CFStringCreateFromUnichar(ch); + if (![s diff_containsObjectForKey:(NSString *)c]) { + [s diff_setUnsignedIntegerValue:0 forKey:(NSString *)c]; + } + CFRelease(c); + } + + NSUInteger i = 0; + for (cnt = 0; cnt < length; cnt++) { + ch = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, cnt); + c = diff_CFStringCreateFromUnichar(ch); + NSUInteger value = [s diff_unsignedIntegerForKey:(NSString *)c] | (1 << (pattern.length - i - 1)); + [s diff_setUnsignedIntegerValue:value forKey:(NSString *)c]; + i++; + CFRelease(c); + } + return s; +} + + +#pragma mark Patch Functions +// PATCH FUNCTIONS + + +/** + * Increase the context until it is unique, + * but don't let the pattern expand beyond Match_MaxBits. + * @param patch The patch to grow. + * @param text Source text. + */ +- (void)patch_addContextToPatch:(Patch *)patch + sourceText:(NSString *)text; +{ + if (text.length == 0) { + return; + } + NSString *pattern = [text substringWithRange:NSMakeRange(patch.start2, patch.length1)]; + NSUInteger padding = 0; + + // Look for the first and last matches of pattern in text. If two + // different matches are found, increase the pattern length. + while ([text rangeOfString:pattern options:NSLiteralSearch].location + != [text rangeOfString:pattern options:(NSLiteralSearch | NSBackwardsSearch)].location + && pattern.length < (Match_MaxBits - Patch_Margin - Patch_Margin)) { + padding += Patch_Margin; + pattern = [text diff_javaSubstringFromStart:MAX_OF_CONST_AND_DIFF(0, patch.start2, padding) + toEnd:MIN(text.length, patch.start2 + patch.length1 + padding)]; + } + // Add one chunk for good luck. + padding += Patch_Margin; + + // Add the prefix. + NSString *prefix = [text diff_javaSubstringFromStart:MAX_OF_CONST_AND_DIFF(0, patch.start2, padding) + toEnd:patch.start2]; + if (prefix.length != 0) { + [patch.diffs insertObject:[Diff diffWithOperation:DIFF_EQUAL andText:prefix] atIndex:0]; + } + // Add the suffix. + NSString *suffix = [text diff_javaSubstringFromStart:(patch.start2 + patch.length1) + toEnd:MIN(text.length, patch.start2 + patch.length1 + padding)]; + if (suffix.length != 0) { + [patch.diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:suffix]]; + } + + // Roll back the start points. + patch.start1 -= prefix.length; + patch.start2 -= prefix.length; + // Extend the lengths. + patch.length1 += prefix.length + suffix.length; + patch.length2 += prefix.length + suffix.length; +} + +/** + * Compute a list of patches to turn text1 into text2. + * A set of diffs will be computed. + * @param text1 Old text. + * @param text2 New text. + * @return NSMutableArray of Patch objects. + */ +- (NSMutableArray *)patch_makeFromOldString:(NSString *)text1 + andNewString:(NSString *)text2; +{ + // Check for null inputs. + if (text1 == nil || text2 == nil) { + NSLog(@"Null inputs. (patch_make)"); + return nil; + } + + // No diffs provided, compute our own. + NSMutableArray *diffs = [self diff_mainOfOldString:text1 andNewString:text2 checkLines:YES]; + if (diffs.count > 2) { + [self diff_cleanupSemantic:diffs]; + [self diff_cleanupEfficiency:diffs]; + } + + return [self patch_makeFromOldString:text1 andDiffs:diffs]; +} + +/** + * Compute a list of patches to turn text1 into text2. + * text1 will be derived from the provided diffs. + * @param diffs NSMutableArray of Diff objects for text1 to text2. + * @return NSMutableArray of Patch objects. + */ +- (NSMutableArray *)patch_makeFromDiffs:(NSMutableArray *)diffs; +{ + // Check for nil inputs not needed since nil can't be passed in C#. + // No origin NSString *provided, comAdde our own. + NSString *text1 = [self diff_text1:diffs]; + return [self patch_makeFromOldString:text1 andDiffs:diffs]; +} + +/** + * Compute a list of patches to turn text1 into text2. + * text2 is ignored, diffs are the delta between text1 and text2. + * @param text1 Old text + * @param text2 New text + * @param diffs NSMutableArray of Diff objects for text1 to text2. + * @return NSMutableArray of Patch objects. + * @deprecated Prefer -patch_makeFromOldString:diffs:. + */ +- (NSMutableArray *)patch_makeFromOldString:(NSString *)text1 + newString:(NSString *)text2 + diffs:(NSMutableArray *)diffs; +{ + // Check for null inputs. + if (text1 == nil || text2 == nil) { + NSLog(@"Null inputs. (patch_make)"); + return nil; + } + + return [self patch_makeFromOldString:text1 andDiffs:diffs]; +} + +/** + * Compute a list of patches to turn text1 into text2. + * text2 is not provided, diffs are the delta between text1 and text2. + * @param text1 Old text. + * @param diffs NSMutableArray of Diff objects for text1 to text2. + * @return NSMutableArray of Patch objects. + */ +- (NSMutableArray *)patch_makeFromOldString:(NSString *)text1 + andDiffs:(NSMutableArray *)diffs; +{ + // Check for null inputs. + if (text1 == nil) { + NSLog(@"Null inputs. (patch_make)"); + return nil; + } + + NSMutableArray *patches = [NSMutableArray array]; + if (diffs.count == 0) { + return patches; // Get rid of the nil case. + } + Patch *patch = [[Patch new] autorelease]; + NSUInteger char_count1 = 0; // Number of characters into the text1 NSString. + NSUInteger char_count2 = 0; // Number of characters into the text2 NSString. + // Start with text1 (prepatch_text) and apply the diffs until we arrive at + // text2 (postpatch_text). We recreate the patches one by one to determine + // context info. + NSString *prepatch_text = [text1 retain]; + NSMutableString *postpatch_text = [text1 mutableCopy]; + for (Diff *aDiff in diffs) { + if (patch.diffs.count == 0 && aDiff.operation != DIFF_EQUAL) { + // A new patch starts here. + patch.start1 = char_count1; + patch.start2 = char_count2; + } + + switch (aDiff.operation) { + case DIFF_INSERT: + [patch.diffs addObject:aDiff]; + patch.length2 += aDiff.text.length; + [postpatch_text insertString:aDiff.text atIndex:char_count2]; + break; + case DIFF_DELETE: + patch.length1 += aDiff.text.length; + [patch.diffs addObject:aDiff]; + [postpatch_text deleteCharactersInRange:NSMakeRange(char_count2, aDiff.text.length)]; + break; + case DIFF_EQUAL: + if (aDiff.text.length <= 2 * Patch_Margin + && [patch.diffs count] != 0 && aDiff != diffs.lastObject) { + // Small equality inside a patch. + [patch.diffs addObject:aDiff]; + patch.length1 += aDiff.text.length; + patch.length2 += aDiff.text.length; + } + + if (aDiff.text.length >= 2 * Patch_Margin) { + // Time for a new patch. + if (patch.diffs.count != 0) { + [self patch_addContextToPatch:patch sourceText:prepatch_text]; + [patches addObject:patch]; + patch = [[Patch new] autorelease]; + // Unlike Unidiff, our patch lists have a rolling context. + // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff + // Update prepatch text & pos to reflect the application of the + // just completed patch. + [prepatch_text release]; + prepatch_text = [postpatch_text copy]; + char_count1 = char_count2; + } + } + break; + } + + // Update the current character count. + if (aDiff.operation != DIFF_INSERT) { + char_count1 += aDiff.text.length; + } + if (aDiff.operation != DIFF_DELETE) { + char_count2 += aDiff.text.length; + } + } + // Pick up the leftover patch if not empty. + if (patch.diffs.count != 0) { + [self patch_addContextToPatch:patch sourceText:prepatch_text]; + [patches addObject:patch]; + } + + [prepatch_text release]; + [postpatch_text release]; + + return patches; +} + +/** + * Given an array of patches, return another array that is identical. + * @param patches NSArray of Patch objects. + * @return NSMutableArray of Patch objects. + */ +- (NSMutableArray *)patch_deepCopy:(NSArray *)patches; +{ + NSMutableArray *patchesCopy = [[NSMutableArray alloc] initWithArray:patches copyItems:YES]; + return patchesCopy; +} + +/** + * Merge a set of patches onto the text. Return a patched text, as well + * as an array of YES/NO values indicating which patches were applied. + * @param patches NSMutableArray of Patch objects + * @param text Old text. + * @return Two element NSArray, containing the new text and an array of + * BOOL values. + */ +- (NSArray *)patch_apply:(NSArray *)sourcePatches + toString:(NSString *)text; +{ + if (sourcePatches.count == 0) { + return [NSArray arrayWithObjects:text, [NSMutableArray array], nil]; + } + + // Deep copy the patches so that no changes are made to originals. + NSMutableArray *patches = [self patch_deepCopy:sourcePatches]; + + NSMutableString *textMutable = [[text mutableCopy] autorelease]; + + NSString *nullPadding = [self patch_addPadding:patches]; + [textMutable insertString:nullPadding atIndex:0]; + [textMutable appendString:nullPadding]; + [self patch_splitMax:patches]; + + NSUInteger x = 0; + // delta keeps track of the offset between the expected and actual + // location of the previous patch. If there are patches expected at + // positions 10 and 20, but the first patch was found at 12, delta is 2 + // and the second patch has an effective expected position of 22. + NSUInteger delta = 0; + BOOL *results = (BOOL *)calloc(patches.count, sizeof(BOOL)); + for (Patch *aPatch in patches) { + NSUInteger expected_loc = aPatch.start2 + delta; + NSString *text1 = [self diff_text1:aPatch.diffs]; + NSUInteger start_loc; + NSUInteger end_loc = NSNotFound; + if (text1.length > Match_MaxBits) { + // patch_splitMax will only provide an oversized pattern + // in the case of a monster delete. + start_loc = [self match_mainForText:textMutable + pattern:[text1 substringWithRange:NSMakeRange(0, Match_MaxBits)] + near:expected_loc]; + if (start_loc != NSNotFound) { + end_loc = [self match_mainForText:textMutable + pattern:[text1 substringFromIndex:text1.length - Match_MaxBits] + near:(expected_loc + text1.length - Match_MaxBits)]; + if (end_loc == NSNotFound || start_loc >= end_loc) { + // Can't find valid trailing context. Drop this patch. + start_loc = NSNotFound; + } + } + } else { + start_loc = [self match_mainForText:textMutable pattern:text1 near:expected_loc]; + } + if (start_loc == NSNotFound) { + // No match found. :( + results[x] = NO; + // Subtract the delta for this failed patch from subsequent patches. + delta -= aPatch.length2 - aPatch.length1; + } else { + // Found a match. :) + results[x] = YES; + delta = start_loc - expected_loc; + NSString *text2; + if (end_loc == NSNotFound) { + text2 = [textMutable diff_javaSubstringFromStart:start_loc + toEnd:MIN(start_loc + text1.length, textMutable.length)]; + } else { + text2 = [textMutable diff_javaSubstringFromStart:start_loc + toEnd:MIN(end_loc + Match_MaxBits, textMutable.length)]; + } + if (text1 == text2) { + // Perfect match, just shove the Replacement text in. + [textMutable replaceCharactersInRange:NSMakeRange(start_loc, text1.length) withString:[self diff_text2:aPatch.diffs]]; + } else { + // Imperfect match. Run a diff to get a framework of equivalent + // indices. + NSMutableArray *diffs = [self diff_mainOfOldString:text1 andNewString:text2 checkLines:NO]; + if (text1.length > Match_MaxBits + && ([self diff_levenshtein:diffs] / (float)text1.length) + > Patch_DeleteThreshold) { + // The end points match, but the content is unacceptably bad. + results[x] = NO; + } else { + [self diff_cleanupSemanticLossless:diffs]; + NSUInteger index1 = 0; + for (Diff *aDiff in aPatch.diffs) { + if (aDiff.operation != DIFF_EQUAL) { + NSUInteger index2 = [self diff_xIndexIn:diffs location:index1]; + if (aDiff.operation == DIFF_INSERT) { + // Insertion + [textMutable insertString:aDiff.text atIndex:(start_loc + index2)]; + } else if (aDiff.operation == DIFF_DELETE) { + // Deletion + [textMutable deleteCharactersInRange:NSMakeRange(start_loc + index2, + ([self diff_xIndexIn:diffs + location:(index1 + aDiff.text.length)] - index2))]; + } + } + if (aDiff.operation != DIFF_DELETE) { + index1 += aDiff.text.length; + } + } + } + } + } + x++; + } + + NSMutableArray *resultsArray = [NSMutableArray arrayWithCapacity:patches.count]; + for (NSUInteger i = 0; i < patches.count; i++) { + [resultsArray addObject:[NSNumber numberWithBool:(results[i])]]; + } + + if (results != NULL) { + free(results); + } + + // Strip the padding off. + text = [textMutable substringWithRange:NSMakeRange(nullPadding.length, + textMutable.length - 2 * nullPadding.length)]; + [patches release]; + return [NSArray arrayWithObjects:text, resultsArray, nil]; +} + +/** + * Add some padding on text start and end so that edges can match something. + * Intended to be called only from within patch_apply. + * @param patches NSMutableArray of Patch objects. + * @return The padding NSString added to each side. + */ +- (NSString *)patch_addPadding:(NSMutableArray *)patches; +{ + uint16_t paddingLength = Patch_Margin; + NSMutableString *nullPadding = [NSMutableString string]; + for (UniChar x = 1; x <= paddingLength; x++) { + CFStringAppendCharacters((CFMutableStringRef)nullPadding, &x, 1); + } + + // Bump all the patches forward. + for (Patch *aPatch in patches) { + aPatch.start1 += paddingLength; + aPatch.start2 += paddingLength; + } + + // Add some padding on start of first diff. + Patch *patch = [patches objectAtIndex:0]; + NSMutableArray *diffs = patch.diffs; + if (diffs.count == 0 || ((Diff *)[diffs objectAtIndex:0]).operation != DIFF_EQUAL) { + // Add nullPadding equality. + [diffs insertObject:[Diff diffWithOperation:DIFF_EQUAL andText:nullPadding] atIndex:0]; + patch.start1 -= paddingLength; // Should be 0. + patch.start2 -= paddingLength; // Should be 0. + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } else if (paddingLength > ((Diff *)[diffs objectAtIndex:0]).text.length) { + // Grow first equality. + Diff *firstDiff = [diffs objectAtIndex:0]; + NSUInteger extraLength = paddingLength - firstDiff.text.length; + firstDiff.text = [[nullPadding substringFromIndex:(firstDiff.text.length)] + stringByAppendingString:firstDiff.text]; + patch.start1 -= extraLength; + patch.start2 -= extraLength; + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + // Add some padding on end of last diff. + patch = patches.lastObject; + diffs = patch.diffs; + if (diffs.count == 0 || ((Diff *)diffs.lastObject).operation != DIFF_EQUAL) { + // Add nullPadding equality. + [diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:nullPadding]]; + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } else if (paddingLength > ((Diff *)diffs.lastObject).text.length) { + // Grow last equality. + Diff *lastDiff = diffs.lastObject; + NSUInteger extraLength = paddingLength - lastDiff.text.length; + lastDiff.text = [lastDiff.text stringByAppendingString:[nullPadding substringWithRange:NSMakeRange(0, extraLength)]]; + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + return nullPadding; +} + +/** + * Look through the patches and break up any which are longer than the + * maximum limit of the match algorithm. + * Intended to be called only from within patch_apply. + * @param patches NSMutableArray of Patch objects. + */ +- (void)patch_splitMax:(NSMutableArray *)patches; +{ + NSUInteger patch_size = Match_MaxBits; + for (NSUInteger x = 0; x < patches.count; x++) { + if (((Patch *)[patches objectAtIndex:x]).length1 <= patch_size) { + continue; + } + Patch *bigpatch = [[patches objectAtIndex:x] retain]; + // Remove the big old patch. + splice(patches, x--, 1, nil); + NSUInteger start1 = bigpatch.start1; + NSUInteger start2 = bigpatch.start2; + NSString *precontext = @""; + while (bigpatch.diffs.count != 0) { + // Create one of several smaller patches. + Patch *patch = [[Patch new] autorelease]; + BOOL empty = YES; + patch.start1 = start1 - precontext.length; + patch.start2 = start2 - precontext.length; + if (precontext.length != 0) { + patch.length1 = patch.length2 = precontext.length; + [patch.diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:precontext]]; + } + while (bigpatch.diffs.count != 0 + && patch.length1 < patch_size - self.Patch_Margin) { + Operation diff_type = ((Diff *)[bigpatch.diffs objectAtIndex:0]).operation; + NSString *diff_text = ((Diff *)[bigpatch.diffs objectAtIndex:0]).text; + if (diff_type == DIFF_INSERT) { + // Insertions are harmless. + patch.length2 += diff_text.length; + start2 += diff_text.length; + [patch.diffs addObject:[bigpatch.diffs objectAtIndex:0]]; + [bigpatch.diffs removeObjectAtIndex:0]; + empty = NO; + } else if (diff_type == DIFF_DELETE && patch.diffs.count == 1 + && ((Diff *)[patch.diffs objectAtIndex:0]).operation == DIFF_EQUAL + && diff_text.length > 2 * patch_size) { + // This is a large deletion. Let it pass in one chunk. + patch.length1 += diff_text.length; + start1 += diff_text.length; + empty = NO; + [patch.diffs addObject:[Diff diffWithOperation:diff_type andText:diff_text]]; + [bigpatch.diffs removeObjectAtIndex:0]; + } else { + // Deletion or equality. Only take as much as we can stomach. + diff_text = [diff_text substringWithRange:NSMakeRange(0, + MIN(diff_text.length, + (patch_size - patch.length1 - Patch_Margin)))]; + patch.length1 += diff_text.length; + start1 += diff_text.length; + if (diff_type == DIFF_EQUAL) { + patch.length2 += diff_text.length; + start2 += diff_text.length; + } else { + empty = NO; + } + [patch.diffs addObject:[Diff diffWithOperation:diff_type andText:diff_text]]; + if (diff_text == ((Diff *)[bigpatch.diffs objectAtIndex:0]).text) { + [bigpatch.diffs removeObjectAtIndex:0]; + } else { + Diff *firstDiff = [bigpatch.diffs objectAtIndex:0]; + firstDiff.text = [firstDiff.text substringFromIndex:diff_text.length]; + } + } + } + // Compute the head context for the next patch. + precontext = [self diff_text2:patch.diffs]; + precontext = [precontext substringFromIndex:MAX_OF_CONST_AND_DIFF(0, precontext.length, Patch_Margin)]; + + NSString *postcontext = nil; + // Append the end context for this patch. + if ([self diff_text1:bigpatch.diffs].length > Patch_Margin) { + postcontext = [[self diff_text1:bigpatch.diffs] + substringWithRange:NSMakeRange(0, Patch_Margin)]; + } else { + postcontext = [self diff_text1:bigpatch.diffs]; + } + + if (postcontext.length != 0) { + patch.length1 += postcontext.length; + patch.length2 += postcontext.length; + if (patch.diffs.count != 0 + && ((Diff *)[patch.diffs objectAtIndex:(patch.diffs.count - 1)]).operation + == DIFF_EQUAL) { + Diff *lastDiff = [patch.diffs lastObject]; + lastDiff.text = [lastDiff.text stringByAppendingString:postcontext]; + } else { + [patch.diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:postcontext]]; + } + } + if (!empty) { + splice(patches, ++x, 0, [NSMutableArray arrayWithObject:patch]); + } + } + + [bigpatch release]; + + } +} + +/** + * Take a list of patches and return a textual representation. + * @param patches NSMutableArray of Patch objects. + * @return Text representation of patches. + */ +- (NSString *)patch_toText:(NSMutableArray *)patches; +{ + NSMutableString *text = [NSMutableString string]; + for (Patch *aPatch in patches) { + [text appendString:[aPatch description]]; + } + return text; +} + +/** + * Parse a textual representation of patches and return a NSMutableArray of + * Patch objects. + * @param textline Text representation of patches. + * @param error NSError if invalid input. + * @return NSMutableArray of Patch objects. + */ +- (NSMutableArray *)patch_fromText:(NSString *)textline + error:(NSError **)error; +{ + NSMutableArray *patches = [NSMutableArray array]; + if (textline.length == 0) { + return patches; + } + NSArray *text = [textline componentsSeparatedByString:@"\n"]; + NSUInteger textPointer = 0; + Patch *patch; + //NSString *patchHeader = @"^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"; + NSString *patchHeaderStart = @"@@ -"; + NSString *patchHeaderMid = @"+"; + NSString *patchHeaderEnd = @"@@"; + NSString *optionalValueDelimiter = @","; + BOOL scanSuccess, hasOptional; + NSInteger scannedValue, optionalValue; + NSDictionary *errorDetail = nil; + + unichar sign; + NSString *line; + while (textPointer < text.count) { + NSString *thisLine = [text objectAtIndex:textPointer]; + NSScanner *theScanner = [NSScanner scannerWithString:thisLine]; + patch = [[Patch new] autorelease]; + + scanSuccess = ([theScanner scanString:patchHeaderStart intoString:NULL] + && [theScanner scanInteger:&scannedValue]); + + if (scanSuccess) { + patch.start1 = scannedValue; + + hasOptional = [theScanner scanString:optionalValueDelimiter intoString:NULL]; + + if (hasOptional) { + // First set has an optional value. + scanSuccess = [theScanner scanInteger:&optionalValue]; + if (scanSuccess) { + if (optionalValue == 0) { + patch.length1 = 0; + } else { + patch.start1--; + patch.length1 = optionalValue; + } + } + } else { + patch.start1--; + patch.length1 = 1; + } + + if (scanSuccess) { + scanSuccess = ([theScanner scanString:patchHeaderMid intoString:NULL] + && [theScanner scanInteger:&scannedValue]); + + if (scanSuccess) { + patch.start2 = scannedValue; + + hasOptional = [theScanner scanString:optionalValueDelimiter intoString:NULL]; + + if (hasOptional) { + // Second set has an optional value. + scanSuccess = [theScanner scanInteger:&optionalValue]; + if (scanSuccess) { + if (optionalValue == 0) { + patch.length2 = 0; + } else { + patch.start2--; + patch.length2 = optionalValue; + } + } + } else { + patch.start2--; + patch.length2 = 1; + } + + if (scanSuccess) { + scanSuccess = ([theScanner scanString:patchHeaderEnd intoString:NULL] + && [theScanner isAtEnd] == YES); + } + } + } + } + + if (!scanSuccess) { + if (error != NULL) { + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Invalid patch string: %@", @"Error"), + [text objectAtIndex:textPointer]], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:104 userInfo:errorDetail]; + } + return nil; + } + + [patches addObject:patch]; + + textPointer++; + + while (textPointer < text.count) { + @try { + sign = [[text objectAtIndex:textPointer] characterAtIndex:0]; + } + @catch (NSException *e) { + // Blank line? Whatever. + textPointer++; + continue; + } + line = [[[text objectAtIndex:textPointer] substringFromIndex:1] + diff_stringByReplacingPercentEscapesForEncodeUriCompatibility]; + if (sign == '-') { + // Deletion. + [patch.diffs addObject:[Diff diffWithOperation:DIFF_DELETE andText:line]]; + } else if (sign == '+') { + // Insertion. + [patch.diffs addObject:[Diff diffWithOperation:DIFF_INSERT andText:line]]; + } else if (sign == ' ') { + // Minor equality. + [patch.diffs addObject:[Diff diffWithOperation:DIFF_EQUAL andText:line]]; + } else if (sign == '@') { + // Start of next patch. + break; + } else { + // WTF? + if (error != NULL) { + errorDetail = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:NSLocalizedString(@"Invalid patch mode '%C' in: %@", @"Error"), sign, line], + NSLocalizedDescriptionKey, nil]; + *error = [NSError errorWithDomain:@"DiffMatchPatchErrorDomain" code:104 userInfo:errorDetail]; + } + return nil; + } + textPointer++; + } + } + return patches; +} + +@end diff --git a/Externals/google-diff-match-patch/DiffMatchPatchCFUtilities.c b/Externals/google-diff-match-patch/DiffMatchPatchCFUtilities.c new file mode 100755 index 0000000..336d766 --- /dev/null +++ b/Externals/google-diff-match-patch/DiffMatchPatchCFUtilities.c @@ -0,0 +1,587 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#include + +#include "DiffMatchPatchCFUtilities.h" + +#include "MinMaxMacros.h" +#include +#include +#include + +CFStringRef diff_CFStringCreateSubstring(CFStringRef text, CFIndex start_index, CFIndex length); +CFRange diff_RightSubstringRange(CFIndex text_length, CFIndex new_length); +CFStringRef diff_CFStringCreateRightSubstring(CFStringRef text, CFIndex text_length, CFIndex new_length); +CFRange diff_LeftSubstringRange(CFIndex new_length); +CFStringRef diff_CFStringCreateLeftSubstring(CFStringRef text, CFIndex new_length); +CFStringRef diff_CFStringCreateSubstringWithStartIndex(CFStringRef text, CFIndex start_index); +CFStringRef diff_CFStringCreateJavaSubstring(CFStringRef s, CFIndex begin, CFIndex end); +CFStringRef diff_CFStringCreateByCombiningTwoStrings(CFStringRef best_common_part1, CFStringRef best_common_part2); +Boolean diff_regExMatch(CFStringRef text, const regex_t *re); + +CFArrayRef diff_halfMatchICreate(CFStringRef longtext, CFStringRef shorttext, CFIndex i); + +// Utility functions +CFStringRef diff_CFStringCreateFromUnichar(UniChar ch) { + CFStringRef c = CFStringCreateWithCharacters(kCFAllocatorDefault, &ch, 1); + CFMakeCollectable(c); + return c; +} + +CFStringRef diff_CFStringCreateSubstring(CFStringRef text, CFIndex start_index, CFIndex length) { + CFRange substringRange; + substringRange.length = length; + substringRange.location = start_index; + + CFStringRef substring = CFStringCreateWithSubstring(kCFAllocatorDefault, text, substringRange); + CFMakeCollectable(substring); + + return substring; +} + +CFRange diff_RightSubstringRange(CFIndex text_length, CFIndex new_length) { + CFRange substringRange; + substringRange.length = new_length; + substringRange.location = text_length - new_length; + return substringRange; +} + +CFStringRef diff_CFStringCreateRightSubstring(CFStringRef text, CFIndex text_length, CFIndex new_length) { + return diff_CFStringCreateSubstring(text, text_length - new_length, new_length); +} + +CFRange diff_LeftSubstringRange(CFIndex new_length) { + CFRange substringRange; + substringRange.length = new_length; + substringRange.location = 0; + return substringRange; +} + +CFStringRef diff_CFStringCreateLeftSubstring(CFStringRef text, CFIndex new_length) { + return diff_CFStringCreateSubstring(text, 0, new_length); +} + +CFStringRef diff_CFStringCreateSubstringWithStartIndex(CFStringRef text, CFIndex start_index) { + return diff_CFStringCreateSubstring(text, start_index, (CFStringGetLength(text) - start_index)); +} + +CFStringRef diff_CFStringCreateJavaSubstring(CFStringRef s, CFIndex begin, CFIndex end) { + return diff_CFStringCreateSubstring(s, begin, end - begin); +} + +CFStringRef diff_CFStringCreateByCombiningTwoStrings(CFStringRef best_common_part1, CFStringRef best_common_part2) { + CFIndex best_common_length; + CFMutableStringRef best_common_mutable; + best_common_length = CFStringGetLength(best_common_part1) + CFStringGetLength(best_common_part2); + best_common_mutable = CFStringCreateMutableCopy(kCFAllocatorDefault, best_common_length, best_common_part1); + CFMakeCollectable(best_common_mutable); + CFStringAppend(best_common_mutable, best_common_part2); + return best_common_mutable; +} + +Boolean diff_regExMatch(CFStringRef text, const regex_t *re) { + //TODO(jan): Using regex.h is far from optimal. Find an alternative. + Boolean isMatch; + const char *bytes; + char *localBuffer = NULL; + char *textCString = NULL; + // We are only interested in line endings anyway so ASCII is fine. + CFStringEncoding encoding = kCFStringEncodingASCII; + + bytes = CFStringGetCStringPtr(text, encoding); + + if (bytes == NULL) { + Boolean success; + CFIndex length; + CFIndex usedBufferLength; + CFIndex textLength = CFStringGetLength(text); + CFRange rangeToProcess = CFRangeMake(0, textLength); + + success = (CFStringGetBytes(text, rangeToProcess, encoding, '?', false, NULL, LONG_MAX, &usedBufferLength) > 0); + if (success) { + length = usedBufferLength + 1; + + localBuffer = calloc(length, sizeof(char)); + success = (CFStringGetBytes(text, rangeToProcess, encoding, '?', false, (UInt8 *)localBuffer, length, NULL) > 0); + + if (success) { + textCString = localBuffer; + } + } + } else { + textCString = (char *)bytes; + } + + if (textCString != NULL) { + isMatch = (regexec(re, textCString, 0, NULL, 0) == 0); + } else { + isMatch = false; + //assert(0); + } + + if (localBuffer != NULL) { + free(localBuffer); + } + + return isMatch; +} + + +/** + * Determine the common prefix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the start of each string. + */ +CFIndex diff_commonPrefix(CFStringRef text1, CFStringRef text2) { + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + CFIndex text1_length = CFStringGetLength(text1); + CFIndex text2_length = CFStringGetLength(text2); + + CFStringInlineBuffer text1_inlineBuffer, text2_inlineBuffer; + CFStringInitInlineBuffer(text1, &text1_inlineBuffer, CFRangeMake(0, text1_length)); + CFStringInitInlineBuffer(text2, &text2_inlineBuffer, CFRangeMake(0, text2_length)); + + UniChar char1, char2; + CFIndex n = MIN(text1_length, text2_length); + + for (CFIndex i = 0; i < n; i++) { + char1 = CFStringGetCharacterFromInlineBuffer(&text1_inlineBuffer, i); + char2 = CFStringGetCharacterFromInlineBuffer(&text2_inlineBuffer, i); + + if (char1 != char2) { + return i; + } + } + + return n; +} + +/** + * Determine the common suffix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of each string. + */ +CFIndex diff_commonSuffix(CFStringRef text1, CFStringRef text2) { + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + CFIndex text1_length = CFStringGetLength(text1); + CFIndex text2_length = CFStringGetLength(text2); + + CFStringInlineBuffer text1_inlineBuffer, text2_inlineBuffer; + CFStringInitInlineBuffer(text1, &text1_inlineBuffer, CFRangeMake(0, text1_length)); + CFStringInitInlineBuffer(text2, &text2_inlineBuffer, CFRangeMake(0, text2_length)); + + UniChar char1, char2; + CFIndex n = MIN(text1_length, text2_length); + + for (CFIndex i = 1; i <= n; i++) { + char1 = CFStringGetCharacterFromInlineBuffer(&text1_inlineBuffer, (text1_length - i)); + char2 = CFStringGetCharacterFromInlineBuffer(&text2_inlineBuffer, (text2_length - i)); + + if (char1 != char2) { + return i - 1; + } + } + return n; +} + +/** + * Determine if the suffix of one CFStringRef is the prefix of another. + * @param text1 First CFStringRef. + * @param text2 Second CFStringRef. + * @return The number of characters common to the end of the first + * CFStringRef and the start of the second CFStringRef. + */ +CFIndex diff_commonOverlap(CFStringRef text1, CFStringRef text2) { + CFIndex common_overlap = 0; + + // Cache the text lengths to prevent multiple calls. + CFIndex text1_length = CFStringGetLength(text1); + CFIndex text2_length = CFStringGetLength(text2); + + // Eliminate the nil case. + if (text1_length == 0 || text2_length == 0) { + return 0; + } + + // Truncate the longer CFStringRef. + CFStringRef text1_trunc; + CFStringRef text2_trunc; + CFIndex text1_trunc_length; + if (text1_length > text2_length) { + text1_trunc_length = text2_length; + text1_trunc = diff_CFStringCreateRightSubstring(text1, text1_length, text1_trunc_length); + + text2_trunc = CFRetain(text2); + } else if (text1_length < text2_length) { + text1_trunc_length = text1_length; + text1_trunc = CFRetain(text1); + + CFIndex text2_trunc_length = text1_length; + text2_trunc = diff_CFStringCreateLeftSubstring(text2, text2_trunc_length); + } else { + text1_trunc_length = text1_length; + text1_trunc = CFRetain(text1); + + text2_trunc = CFRetain(text2); + } + + CFIndex text_length = MIN(text1_length, text2_length); + // Quick check for the worst case. + if (text1_trunc == text2_trunc) { + common_overlap = text_length; + } else { + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: http://neil.fraser.name/news/2010/11/04/ + CFIndex best = 0; + CFIndex length = 1; + while (true) { + CFStringRef pattern = diff_CFStringCreateRightSubstring(text1_trunc, text1_trunc_length, length); + CFRange foundRange = CFStringFind(text2_trunc, pattern, 0); + CFRelease(pattern); + + CFIndex found = foundRange.location; + if (found == kCFNotFound) { + common_overlap = best; + break; + } + length += found; + + CFStringRef text1_sub = diff_CFStringCreateRightSubstring(text1_trunc, text1_trunc_length, length); + CFStringRef text2_sub = diff_CFStringCreateLeftSubstring(text2_trunc, length); + + if (found == 0 || (CFStringCompare(text1_sub, text2_sub, 0) == kCFCompareEqualTo)) { + best = length; + length++; + } + + CFRelease(text1_sub); + CFRelease(text2_sub); + } + } + + CFRelease(text1_trunc); + CFRelease(text2_trunc); + return common_overlap; +} + +/** + * Do the two texts share a Substring which is at least half the length of + * the longer text? + * This speedup can produce non-minimal diffs. + * @param text1 First CFStringRef. + * @param text2 Second CFStringRef. + * @param diffTimeout Time limit for diff. + * @return Five element String array, containing the prefix of text1, the + * suffix of text1, the prefix of text2, the suffix of text2 and the + * common middle. Or NULL if there was no match. + */ +CFArrayRef diff_halfMatchCreate(CFStringRef text1, CFStringRef text2, const float diffTimeout) { + if (diffTimeout <= 0) { + // Don't risk returning a non-optimal diff if we have unlimited time. + return NULL; + } + CFStringRef longtext = CFStringGetLength(text1) > CFStringGetLength(text2) ? text1 : text2; + CFStringRef shorttext = CFStringGetLength(text1) > CFStringGetLength(text2) ? text2 : text1; + if (CFStringGetLength(longtext) < 4 || CFStringGetLength(shorttext) * 2 < CFStringGetLength(longtext)) { + return NULL; // Pointless. + } + + // First check if the second quarter is the seed for a half-match. + CFArrayRef hm1 = diff_halfMatchICreate(longtext, shorttext, + (CFStringGetLength(longtext) + 3) / 4); + // Check again based on the third quarter. + CFArrayRef hm2 = diff_halfMatchICreate(longtext, shorttext, + (CFStringGetLength(longtext) + 1) / 2); + CFArrayRef hm; + if (hm1 == NULL && hm2 == NULL) { + return NULL; + } else if (hm2 == NULL) { + hm = CFRetain(hm1); + } else if (hm1 == NULL) { + hm = CFRetain(hm2); + } else { + // Both matched. Select the longest. + hm = CFStringGetLength(CFArrayGetValueAtIndex(hm1, 4)) > CFStringGetLength(CFArrayGetValueAtIndex(hm2, 4)) ? CFRetain(hm1) : CFRetain(hm2); + } + + if (hm1 != NULL) { + CFRelease(hm1); + } + if (hm2 != NULL) { + CFRelease(hm2); + } + + // A half-match was found, sort out the return data. + if (CFStringGetLength(text1) > CFStringGetLength(text2)) { + return hm; + //return new CFStringRef[]{hm[0], hm[1], hm[2], hm[3], hm[4]}; + } else { + // { hm[0], hm[1], hm[2], hm[3], hm[4] } + // => { hm[2], hm[3], hm[0], hm[1], hm[4] } + + CFMutableArrayRef hm_mutable = CFArrayCreateMutableCopy(kCFAllocatorDefault, CFArrayGetCount(hm), hm); + CFMakeCollectable(hm_mutable); + + CFRelease(hm); + + CFArrayExchangeValuesAtIndices(hm_mutable, 0, 2); + CFArrayExchangeValuesAtIndices(hm_mutable, 1, 3); + return hm_mutable; + } +} + +/** + * Does a Substring of shorttext exist within longtext such that the + * Substring is at least half the length of longtext? + * @param longtext Longer CFStringRef. + * @param shorttext Shorter CFStringRef. + * @param i Start index of quarter length Substring within longtext. + * @return Five element CFStringRef array, containing the prefix of longtext, the + * suffix of longtext, the prefix of shorttext, the suffix of shorttext + * and the common middle. Or NULL if there was no match. + */ +CFArrayRef diff_halfMatchICreate(CFStringRef longtext, CFStringRef shorttext, CFIndex i) { + // Start with a 1/4 length Substring at position i as a seed. + CFStringRef seed = diff_CFStringCreateSubstring(longtext, i, CFStringGetLength(longtext) / 4); + CFIndex j = -1; + CFStringRef best_common = CFSTR(""); + CFStringRef best_longtext_a = CFSTR(""), best_longtext_b = CFSTR(""); + CFStringRef best_shorttext_a = CFSTR(""), best_shorttext_b = CFSTR(""); + + CFStringRef best_common_part1, best_common_part2; + + CFStringRef longtext_substring, shorttext_substring; + CFIndex shorttext_length = CFStringGetLength(shorttext); + CFRange resultRange; + CFRange rangeToSearch; + rangeToSearch.length = shorttext_length - (j + 1); + rangeToSearch.location = j + 1; + + while (j < CFStringGetLength(shorttext) + && (CFStringFindWithOptions(shorttext, seed, rangeToSearch, 0, &resultRange) == true)) { + j = resultRange.location; + rangeToSearch.length = shorttext_length - (j + 1); + rangeToSearch.location = j + 1; + + longtext_substring = diff_CFStringCreateSubstringWithStartIndex(longtext, i); + shorttext_substring = diff_CFStringCreateSubstringWithStartIndex(shorttext, j); + + CFIndex prefixLength = diff_commonPrefix(longtext_substring, shorttext_substring); + + CFRelease(longtext_substring); + CFRelease(shorttext_substring); + + longtext_substring = diff_CFStringCreateLeftSubstring(longtext, i); + shorttext_substring = diff_CFStringCreateLeftSubstring(shorttext, j); + + CFIndex suffixLength = diff_commonSuffix(longtext_substring, shorttext_substring); + + CFRelease(longtext_substring); + CFRelease(shorttext_substring); + + if (CFStringGetLength(best_common) < suffixLength + prefixLength) { + CFRelease(best_common); + CFRelease(best_longtext_a); + CFRelease(best_longtext_b); + CFRelease(best_shorttext_a); + CFRelease(best_shorttext_b); + + best_common_part1 = diff_CFStringCreateSubstring(shorttext, j - suffixLength, suffixLength); + best_common_part2 = diff_CFStringCreateSubstring(shorttext, j, prefixLength); + + best_common = diff_CFStringCreateByCombiningTwoStrings(best_common_part1, best_common_part2); + + CFRelease(best_common_part1); + CFRelease(best_common_part2); + + best_longtext_a = diff_CFStringCreateLeftSubstring(longtext, i - suffixLength); + best_longtext_b = diff_CFStringCreateSubstringWithStartIndex(longtext, i + prefixLength); + best_shorttext_a = diff_CFStringCreateLeftSubstring(shorttext, j - suffixLength); + best_shorttext_b = diff_CFStringCreateSubstringWithStartIndex(shorttext, j + prefixLength); + } + } + + CFRelease(seed); + + CFArrayRef halfMatchIArray; + if (CFStringGetLength(best_common) * 2 >= CFStringGetLength(longtext)) { + const CFStringRef values[] = { best_longtext_a, best_longtext_b, + best_shorttext_a, best_shorttext_b, best_common }; + halfMatchIArray = CFArrayCreate(kCFAllocatorDefault, (const void **)values, (sizeof(values) / sizeof(values[0])), &kCFTypeArrayCallBacks); + CFMakeCollectable(halfMatchIArray); + } else { + halfMatchIArray = NULL; + } + + CFRelease(best_common); + CFRelease(best_longtext_a); + CFRelease(best_longtext_b); + CFRelease(best_shorttext_a); + CFRelease(best_shorttext_b); + + return halfMatchIArray; +} + +/** + * Split a text into a list of strings. Reduce the texts to a CFStringRef of + * hashes where each Unicode character represents one line. + * @param text CFString to encode. + * @param lineArray CFMutableArray of unique strings. + * @param lineHash Map of strings to indices. + * @return Encoded CFStringRef. + */ +CFStringRef diff_linesToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef lineArray, CFMutableDictionaryRef lineHash) { + #define lineStart lineStartRange.location + #define lineEnd lineEndRange.location + + CFRange lineStartRange; + CFRange lineEndRange; + lineStart = 0; + lineEnd = -1; + CFStringRef line; + CFMutableStringRef chars = CFStringCreateMutable(kCFAllocatorDefault, 0); + + CFIndex textLength = CFStringGetLength(text); + CFIndex hash; + CFNumberRef hashNumber; + + // Walk the text, pulling out a Substring for each line. + // CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, text, CFSTR("\n")) would temporarily double our memory footprint. + // Modifying text would create many large strings. + while (lineEnd < textLength - 1) { + lineStartRange.length = textLength - lineStart; + + if (CFStringFindWithOptions(text, CFSTR("\n"), lineStartRange, 0, &lineEndRange) == false) { + lineEnd = textLength - 1; + } /* else { + lineEnd = lineEndRange.location; + }*/ + + line = diff_CFStringCreateJavaSubstring(text, lineStart, lineEnd + 1); + lineStart = lineEnd + 1; + + if (CFDictionaryContainsKey(lineHash, line)) { + CFDictionaryGetValueIfPresent(lineHash, line, (const void **)&hashNumber); + CFNumberGetValue(hashNumber, kCFNumberCFIndexType, &hash); + const UniChar hashChar = (UniChar)hash; + CFStringAppendCharacters(chars, &hashChar, 1); + } else { + CFArrayAppendValue(lineArray, line); + hash = CFArrayGetCount(lineArray) - 1; + hashNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &hash); + CFMakeCollectable(hashNumber); + CFDictionaryAddValue(lineHash, line, hashNumber); + CFRelease(hashNumber); + const UniChar hashChar = (UniChar)hash; + CFStringAppendCharacters(chars, &hashChar, 1); + } + + CFRelease(line); + } + return chars; + + #undef lineStart + #undef lineEnd +} + +/** + * Given two strings, compute a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 6 (best) to 0 (worst). + * @param one First CFStringRef. + * @param two Second CFStringRef. + * @return The score. + */ +CFIndex diff_cleanupSemanticScore(CFStringRef one, CFStringRef two) { + static Boolean firstRun = true; + static CFCharacterSetRef alphaNumericSet = NULL; + static CFCharacterSetRef whiteSpaceSet = NULL; + static CFCharacterSetRef controlSet = NULL; + static regex_t blankLineEndRegEx; + static regex_t blankLineStartRegEx; + + if (firstRun) { + // Define some regex patterns for matching boundaries. + alphaNumericSet = CFCharacterSetGetPredefined(kCFCharacterSetAlphaNumeric); + whiteSpaceSet = CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline); + controlSet = CFCharacterSetGetPredefined(kCFCharacterSetControl); + int status; + status = regcomp(&blankLineEndRegEx, "\n\r?\n$", REG_EXTENDED | REG_NOSUB); + assert(status == 0); + status = regcomp(&blankLineStartRegEx, "^\r?\n\r?\n", REG_EXTENDED | REG_NOSUB); + assert(status == 0); + firstRun = false; + } + + if (CFStringGetLength(one) == 0 || CFStringGetLength(two) == 0) { + // Edges are the best. + return 6; + } + + // Each port of this function behaves slightly differently due to + // subtle differences in each language's definition of things like + // 'whitespace'. Since this function's purpose is largely cosmetic, + // the choice has been made to use each language's native features + // rather than force total conformity. + UniChar char1 = + CFStringGetCharacterAtIndex(one, (CFStringGetLength(one) - 1)); + UniChar char2 = + CFStringGetCharacterAtIndex(two, 0); + Boolean nonAlphaNumeric1 = + !CFCharacterSetIsCharacterMember(alphaNumericSet, char1); + Boolean nonAlphaNumeric2 = + !CFCharacterSetIsCharacterMember(alphaNumericSet, char2); + Boolean whitespace1 = + nonAlphaNumeric1 && CFCharacterSetIsCharacterMember(whiteSpaceSet, char1); + Boolean whitespace2 = + nonAlphaNumeric2 && CFCharacterSetIsCharacterMember(whiteSpaceSet, char2); + Boolean lineBreak1 = + whitespace1 && CFCharacterSetIsCharacterMember(controlSet, char1); + Boolean lineBreak2 = + whitespace2 && CFCharacterSetIsCharacterMember(controlSet, char2); + Boolean blankLine1 = + lineBreak1 && diff_regExMatch(one, &blankLineEndRegEx); + Boolean blankLine2 = + lineBreak2 && diff_regExMatch(two, &blankLineStartRegEx); + + if (blankLine1 || blankLine2) { + // Five points for blank lines. + return 5; + } else if (lineBreak1 || lineBreak2) { + // Four points for line breaks. + return 4; + } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { + // Three points for end of sentences. + return 3; + } else if (whitespace1 || whitespace2) { + // Two points for whitespace. + return 2; + } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { + // One point for non-alphanumeric. + return 1; + } + return 0; +} diff --git a/Externals/google-diff-match-patch/DiffMatchPatchCFUtilities.h b/Externals/google-diff-match-patch/DiffMatchPatchCFUtilities.h new file mode 100755 index 0000000..87fc7d4 --- /dev/null +++ b/Externals/google-diff-match-patch/DiffMatchPatchCFUtilities.h @@ -0,0 +1,49 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#ifndef _DIFFMATCHPATCHCFUTILITIES_H +#define _DIFFMATCHPATCHCFUTILITIES_H + +CFStringRef diff_CFStringCreateFromUnichar(UniChar ch); +CFStringRef diff_CFStringCreateJavaSubstring(CFStringRef s, CFIndex begin, CFIndex end); + +CFIndex diff_commonPrefix(CFStringRef text1, CFStringRef text2); +CFIndex diff_commonSuffix(CFStringRef text1, CFStringRef text2); +CFIndex diff_commonOverlap(CFStringRef text1, CFStringRef text2); +CFArrayRef diff_halfMatchCreate(CFStringRef text1, CFStringRef text2, const float diffTimeout); +CFArrayRef diff_halfMatchICreate(CFStringRef longtext, CFStringRef shorttext, CFIndex i); + +CFStringRef diff_linesToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef lineArray, CFMutableDictionaryRef lineHash); + +CFIndex diff_cleanupSemanticScore(CFStringRef one, CFStringRef two); + +CF_INLINE void diff_CFStringPrepareUniCharBuffer(CFStringRef string, const UniChar **string_chars, UniChar **string_buffer, CFRange string_range) { + *string_chars = CFStringGetCharactersPtr(string); + if (*string_chars == NULL) { + // Fallback in case CFStringGetCharactersPtr() didn’t work. + *string_buffer = malloc(string_range.length * sizeof(UniChar)); + CFStringGetCharacters(string, string_range, *string_buffer); + *string_chars = *string_buffer; + } +} + +#endif //ifndef _DIFFMATCHPATCHCFUTILITIES_H diff --git a/Externals/google-diff-match-patch/MinMaxMacros.h b/Externals/google-diff-match-patch/MinMaxMacros.h new file mode 100755 index 0000000..c962f1c --- /dev/null +++ b/Externals/google-diff-match-patch/MinMaxMacros.h @@ -0,0 +1,41 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#if !defined(MIN) + #define MIN(A,B) \ + ({__typeof__(A) a = (A); \ + __typeof__(B) b = (B); \ + (a < b) ? a : b; }) +#endif + +#if !defined(MAX) + #define MAX(A,B) \ + ({__typeof__(A) a = (A); \ + __typeof__(B) b = (B); \ + (a > b) ? a : b; }) +#endif + +#if !defined(ABS) + #define ABS(A) \ + ({__typeof__(A) a = (A); \ + (a > 0) ? a : -a; }) +#endif diff --git a/Externals/google-diff-match-patch/NSMutableDictionary+DMPExtensions.h b/Externals/google-diff-match-patch/NSMutableDictionary+DMPExtensions.h new file mode 100755 index 0000000..dbc876d --- /dev/null +++ b/Externals/google-diff-match-patch/NSMutableDictionary+DMPExtensions.h @@ -0,0 +1,47 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import + + +@interface NSMutableDictionary (DMPExtensions) + +- (id)diff_objectForIntegerKey:(NSInteger)keyInteger; +- (id)diff_objectForUnsignedIntegerKey:(NSUInteger)keyUInteger; +- (id)diff_objectForUnicharKey:(unichar)aUnicharKey; + +- (NSInteger)diff_integerForKey:(id)aKey; +- (NSUInteger)diff_unsignedIntegerForKey:(id)aKey; +- (NSInteger)diff_integerForIntegerKey:(NSInteger)keyInteger; +- (NSUInteger)diff_unsignedIntegerForUnicharKey:(unichar)aUnicharKey; + +- (BOOL)diff_containsObjectForKey:(id)aKey; +- (BOOL)diff_containsObjectForUnicharKey:(unichar)aUnicharKey; + +- (void)diff_setIntegerValue:(NSInteger)anInteger forKey:(id)aKey; +- (void)diff_setIntegerValue:(NSInteger)anInteger forIntegerKey:(NSInteger)keyInteger; + +- (void)diff_setUnsignedIntegerValue:(NSUInteger)anUInteger forKey:(id)aKey; +- (void)diff_setUnsignedIntegerValue:(NSUInteger)anUInteger forUnsignedIntegerKey:(NSUInteger)keyUInteger; +- (void)diff_setUnsignedIntegerValue:(NSUInteger)anUInteger forUnicharKey:(unichar)aUnicharKey; + +@end diff --git a/Externals/google-diff-match-patch/NSMutableDictionary+DMPExtensions.m b/Externals/google-diff-match-patch/NSMutableDictionary+DMPExtensions.m new file mode 100755 index 0000000..87eabc7 --- /dev/null +++ b/Externals/google-diff-match-patch/NSMutableDictionary+DMPExtensions.m @@ -0,0 +1,109 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import "NSMutableDictionary+DMPExtensions.h" + +#import "NSString+UnicharUtilities.h" + + +@implementation NSMutableDictionary (DMPExtensions) + +- (id)diff_objectForIntegerKey:(NSInteger)keyInteger; +{ + return [self objectForKey:[NSNumber numberWithInteger:keyInteger]]; +} + +- (id)diff_objectForUnsignedIntegerKey:(NSUInteger)keyUInteger; +{ + return [self objectForKey:[NSNumber numberWithUnsignedInteger:keyUInteger]]; +} + +- (id)diff_objectForUnicharKey:(unichar)aUnicharKey; +{ + return [self objectForKey:[NSString diff_stringFromUnichar:aUnicharKey]]; +} + + +- (NSInteger)diff_integerForKey:(id)aKey; +{ + return [((NSNumber *)[self objectForKey:aKey]) integerValue]; +} + +- (NSUInteger)diff_unsignedIntegerForKey:(id)aKey; +{ + return [((NSNumber *)[self objectForKey:aKey]) unsignedIntegerValue]; +} + +- (NSInteger)diff_integerForIntegerKey:(NSInteger)keyInteger; +{ + return [((NSNumber *)[self objectForKey:[NSNumber numberWithInteger:keyInteger]]) integerValue]; +} + +- (NSUInteger)diff_unsignedIntegerForUnicharKey:(unichar)aUnicharKey; +{ + return [((NSNumber *)[self diff_objectForUnicharKey:aUnicharKey]) unsignedIntegerValue]; +} + + +- (BOOL)diff_containsObjectForKey:(id)aKey; +{ + return ([self objectForKey:aKey] != nil); +} + +- (BOOL)containsObjectForIntegerKey:(NSInteger)keyInteger; +{ + return ([self objectForKey:[NSNumber numberWithInteger:keyInteger]] != nil); +} + +- (BOOL)diff_containsObjectForUnicharKey:(unichar)aUnicharKey; +{ + return ([self diff_objectForUnicharKey:aUnicharKey] != nil); +} + + +- (void)diff_setIntegerValue:(NSInteger)anInteger forKey:(id)aKey; +{ + [self setObject:[NSNumber numberWithInteger:anInteger] forKey:aKey]; +} + +- (void)diff_setIntegerValue:(NSInteger)anInteger forIntegerKey:(NSInteger)keyInteger; +{ + [self setObject:[NSNumber numberWithInteger:anInteger] forKey:[NSNumber numberWithInteger:keyInteger]]; +} + + +- (void)diff_setUnsignedIntegerValue:(NSUInteger)anUInteger forKey:(id)aKey; +{ + [self setObject:[NSNumber numberWithUnsignedInteger:anUInteger] forKey:aKey]; +} + +- (void)diff_setUnsignedIntegerValue:(NSUInteger)anUInteger forUnsignedIntegerKey:(NSUInteger)keyUInteger; +{ + [self setObject:[NSNumber numberWithUnsignedInteger:anUInteger] forKey:[NSNumber numberWithUnsignedInteger:keyUInteger]]; +} + +- (void)diff_setUnsignedIntegerValue:(NSUInteger)anUInteger forUnicharKey:(unichar)aUnicharKey; +{ + [self setObject:[NSNumber numberWithUnsignedInteger:anUInteger] forKey:[NSString diff_stringFromUnichar:aUnicharKey]]; +} + +@end diff --git a/Externals/google-diff-match-patch/NSString+JavaSubstring.h b/Externals/google-diff-match-patch/NSString+JavaSubstring.h new file mode 100755 index 0000000..c3b1a2c --- /dev/null +++ b/Externals/google-diff-match-patch/NSString+JavaSubstring.h @@ -0,0 +1,30 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import + + +@interface NSString (JavaSubstring) + +- (NSString *)diff_javaSubstringFromStart:(NSUInteger)start toEnd:(NSUInteger)end; + +@end diff --git a/Externals/google-diff-match-patch/NSString+JavaSubstring.m b/Externals/google-diff-match-patch/NSString+JavaSubstring.m new file mode 100755 index 0000000..c1e68a4 --- /dev/null +++ b/Externals/google-diff-match-patch/NSString+JavaSubstring.m @@ -0,0 +1,36 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import "NSString+JavaSubstring.h" + +#import "DiffMatchPatchCFUtilities.h" + +@implementation NSString (JavaSubstring) + +- (NSString *)diff_javaSubstringFromStart:(NSUInteger)start toEnd:(NSUInteger)end; +{ + CFStringRef c = diff_CFStringCreateJavaSubstring((CFStringRef)self, (CFIndex)start, (CFIndex)end); + CFMakeCollectable(c); + return [(NSString *)c autorelease]; +} + +@end diff --git a/Externals/google-diff-match-patch/NSString+UnicharUtilities.h b/Externals/google-diff-match-patch/NSString+UnicharUtilities.h new file mode 100755 index 0000000..fe73447 --- /dev/null +++ b/Externals/google-diff-match-patch/NSString+UnicharUtilities.h @@ -0,0 +1,31 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import + + +@interface NSString (UnicharUtilities) + ++ (NSString *)diff_stringFromUnichar:(unichar)ch; +- (NSString *)diff_substringWithCharacterAtIndex:(NSUInteger)anIndex; + +@end diff --git a/Externals/google-diff-match-patch/NSString+UnicharUtilities.m b/Externals/google-diff-match-patch/NSString+UnicharUtilities.m new file mode 100755 index 0000000..0781b21 --- /dev/null +++ b/Externals/google-diff-match-patch/NSString+UnicharUtilities.m @@ -0,0 +1,40 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import "NSString+UnicharUtilities.h" + + +@implementation NSString (UnicharUtilities) + ++ (NSString *)diff_stringFromUnichar:(unichar)ch; +{ + CFStringRef c = CFStringCreateWithCharacters(kCFAllocatorDefault, &ch, 1); + CFMakeCollectable(c); + return [(NSString *)c autorelease]; +} + +- (NSString *)diff_substringWithCharacterAtIndex:(NSUInteger)anIndex; +{ + return [self substringWithRange:NSMakeRange(anIndex, 1)]; +} + +@end diff --git a/Externals/google-diff-match-patch/NSString+UriCompatibility.h b/Externals/google-diff-match-patch/NSString+UriCompatibility.h new file mode 100755 index 0000000..57060cf --- /dev/null +++ b/Externals/google-diff-match-patch/NSString+UriCompatibility.h @@ -0,0 +1,31 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import + + +@interface NSString (UriCompatibility) + +- (NSString *)diff_stringByAddingPercentEscapesForEncodeUriCompatibility; +- (NSString *)diff_stringByReplacingPercentEscapesForEncodeUriCompatibility; + +@end diff --git a/Externals/google-diff-match-patch/NSString+UriCompatibility.m b/Externals/google-diff-match-patch/NSString+UriCompatibility.m new file mode 100755 index 0000000..ac9214e --- /dev/null +++ b/Externals/google-diff-match-patch/NSString+UriCompatibility.m @@ -0,0 +1,63 @@ +/* + * Diff Match and Patch + * + * Copyright 2010 geheimwerk.de. + * http://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: fraser@google.com (Neil Fraser) + * ObjC port: jan@geheimwerk.de (Jan Weiß) + */ + +#import "NSString+UriCompatibility.h" + + +@implementation NSString (UriCompatibility) + +/** + * Escape excluding selected chars for compatability with JavaScript's encodeURI. + * This method produces uppercase hex. + * + * @param str The CFStringRef to escape. + * @return The escaped CFStringRef. + */ +- (NSString *)diff_stringByAddingPercentEscapesForEncodeUriCompatibility; +{ + CFStringRef urlString = CFURLCreateStringByAddingPercentEscapes(NULL, + (CFStringRef)self, + CFSTR(" !~*'();/?:@&=+$,#"), + NULL, + kCFStringEncodingUTF8); + CFMakeCollectable(urlString); + return [(NSString *)urlString autorelease]; +} + +/** + * Unescape all percent escapes. + * + * Example: "%3f" -> "?", "%24" -> "$", etc. + * + * @return The unescaped NSString. + */ +- (NSString *)diff_stringByReplacingPercentEscapesForEncodeUriCompatibility; +{ + CFStringRef decodedString = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, + (CFStringRef)self, + CFSTR(""), + kCFStringEncodingUTF8); + CFMakeCollectable(decodedString); + return [(NSString *)decodedString autorelease]; +} + +@end diff --git a/README.md b/README.md index b09cd89..3c68228 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,40 @@ #BBUncrustifyPlugin-Xcode -Xcode plugin to [uncrustify](http://uncrustify.sourceforge.net) code in Xcode. +Xcode plugin to format code using [Uncrustify](http://uncrustify.sourceforge.net) or [ClangFormat](http://clang.llvm.org/docs/ClangFormat.html). + +![menu](http://f.cl.ly/items/2b2y1v403x2H3U2Y0l2K/BBUncrustifyPlugin-menu.png) + +![preferences](http://f.cl.ly/items/27080O1F3w3g2a3h0m32/BBUncrustifyPlugin.png) + +## BBUncrustifyPlugin 2.0 + +#### New +* Added ClangFormat. +* Preferences window to easily adjust the settings. +* Opening the formatter configuration in an external editor. +* Create factory configuration files (in the preferences window). + +#### Changes from 1.0 to 2.0 + +* Uncrustify configuration: The configuration file must be located in the current directory or any parent directories of the source file. The file is named `.uncrustifyconfig` or `uncrustify.cfg`. Uncrustify looks for the configuration in the additional folders: `Home Folder` and `~/.uncrustify/uncrustify.cfg`. + +* Minimum requirement is Xcode 5.0+ on OS X 10.9+. ## Requirements -Tested with Xcode 4.6+ (also works in Xcode 5) on OS X 10.7 or higher. +Xcode 5.0+ on OS X 10.9+. PS: [This fork](https://github.com/1951FDG/BBUncrustifyPlugin-Xcode) works with Xcode 3. ## Installation -### Compiled Version +#### Compiled Version * The easiest way to install the plugin is to [download the last available release](https://github.com/benoitsan/BBUncrustifyPlugin-Xcode/releases) (Click on the **green button** corresponding to the last version). * Unzip and copy `UncrustifyPlugin.xcplugin` to `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins`. * Relaunch Xcode after the copy. -### Build from Source +#### Build from Source * Build the Xcode project. The plug-in will automatically be installed in `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins`. @@ -24,76 +42,25 @@ PS: [This fork](https://github.com/1951FDG/BBUncrustifyPlugin-Xcode) works with To uninstall, just remove the plugin from `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins` and restart Xcode. -Note: - -* When building on Xcode < 5.1: Build using the scheme `BBUncrustifyPlugin-GC`. This will produce a binary supporting Garbage Collection. **This binary works for Xcode 4 and 5.** - -* When building on Xcode 5.1+: Build using the scheme `BBUncrustifyPlugin-ARC` (Garbage Collection is no more supported with Xcode 5.1+). Because This binary doesn't support Garbage collection, **it will only work on Xcode 5.** - ## How does it work? -* Use the menu `Edit > Uncrustify Selected Files` to uncrustify the selected items in the project navigator. - -* Use the menu `Edit > Uncrustify Active File` to uncrustify the source file actually opened in the editor. +* Use the menu `Edit > Format Selected Files` to format the selected items in the project navigator. -* Use the menu `Edit > Uncrustify Selected Lines` to uncrustify the selected source code (multiple selection is supported). The selection is automatically extended in full lines. If the selection is empty, it uses the line under the cursor. - -PS: Modifications are recorded in the undo. So undo reverts the modifications. - -You can create keyboard shortcuts for the menu items in the [Keyboard Preferences](http://support.apple.com/kb/ph3957) of OS X System Preferences. +* Use the menu `Edit > Format Active File` to format the source file actually opened in the editor. +* Use the menu `Edit > Format Selected Lines` to format the selected source code (multiple selection is supported). The selection is automatically extended in full lines. If the selection is empty, it uses the line under the cursor. -## How to customize the uncrustify configuration? +* Use the menu `Edit > Edit Configuration` to edit the formatter configuration in an external editor. -By default, the plugin uses the configuration file `uncrustify.cfg` found in the bundle. +* Use the menu `Edit > BBUncrustifyPlugin Preferences` to change the plugin preferences. -#### Per user configuration -To customize the configuration, copy the file `uncrustify.cfg` or your own to: +## Notes -1. `~/.uncrustifyconfig` or -2. `~/uncrustify.cfg` or -3. `~/.uncrustify/uncrustify.cfg` +When the code is reformated, the modifications are recorded in the undo. So undo reverts the modifications. -#### Per project configuration -A configuration file named `uncrustify.cfg` or `.uncrustifyconfig` can be defined for a project, a workspace or a Xcode container folder (folder with the yellow icon in the Xcode files navigator). +The Preferences window contains detailed informations to customize the formatter settings. -The lookup of the configuration file is made in this order: - -1. Closest Xcode container folder ancestor. -2. Closest Xcode project file ('.xcodeproj') folder ancestor. -3. Closest Xcode workspace file ('.xcworkspace') folder ancestor. - -Example: - -``` -|-- workspace.xcworkspace -|-- uncrustify.cfg -|-- project folder -|---- project.xcodeproj -|---- Third Party Library Folder -|------ uncrustify.cfg -|-- An other project folder -|---- An other project.xcodeproj -|---- uncrustify.cfg -```` - -### Using UncrustifyX - -A more easy way to edit the configuration is to use the Mac appplication [UncrustifyX](https://github.com/ryanmaxwell/UncrustifyX). - -Once UncrustifyX is installed, the plugin will add a menu item `Open with UncrustifyX` to open the actual source code and configuration in UncrustifyX. - -## Xcode Normalization - -After uncrustification, the plugin: - -* performs a syntax-aware indenting if checked in the Xcode preferences (Preferences > Text Editing > Indentation > Syntax-aware indenting). - -* Trims trailing whitespaces and white-only lines if checked in the Xcode preferences (Preferences > Text Editing > Editing). - -You can disable this feature by setting the default key `uncrustify_plugin_codeFormattingScheme`to `1` (`0` or not defined means enabled). - - defaults write com.apple.dt.Xcode uncrustify_plugin_codeFormattingScheme -int 1 +You can create keyboard shortcuts for the menu items in the [Keyboard Preferences](http://support.apple.com/kb/ph3957) of OS X System Preferences. ## Creator diff --git a/Resources/Info.plist b/Resources/Info.plist index 869ad2c..ec2d22e 100755 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.0.5 + 2.0 CFBundleSignature ???? CFBundleVersion - 1.0.5 + 2.0 CFPlugInDynamicRegisterFunction CFPlugInDynamicRegistration diff --git a/Resources/XCFPreferencesWindowController.xib b/Resources/XCFPreferencesWindowController.xib new file mode 100644 index 0000000..606a652 --- /dev/null +++ b/Resources/XCFPreferencesWindowController.xib @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The configuration file must be located in the current directory or any parent directories of the source file. +- For Clang, the file is named `.clang-format` or `_clang-format`. +- For Uncrustify, the file is named `.uncrustifyconfig` or `uncrustify.cfg`. Uncrustify looks for the configuration in the additional folders: `Home Folder` and `~/.uncrustify/uncrustify.cfg`. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/clang-format b/Resources/clang-format new file mode 100755 index 0000000..e9b13b1 Binary files /dev/null and b/Resources/clang-format differ