├── AngelDiff.xcodeproj └── project.pbxproj ├── AngelDiff_Prefix.pch ├── AppDelegate.h ├── AppDelegate.m ├── English.lproj ├── InfoPlist.strings └── MainMenu.xib ├── Info.plist ├── README.txt ├── Testfile1.diff ├── Testfile1.merged.txt ├── Testfile1.txt ├── Testfile1.udiff ├── Testfile2.txt ├── UKDiffParser.h ├── UKDiffParser.m ├── UKDiffView.h ├── UKDiffView.m ├── UKDiffViewChooseLeft.pdf ├── UKDiffViewChooseRight.pdf ├── UKObjectLogger.h ├── UKObjectLogger.m └── main.m /AngelDiff.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 11 | 553744150E4430ED00B1011D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 553744140E4430ED00B1011D /* AppDelegate.m */; }; 12 | 5537441B0E4431B600B1011D /* UKDiffParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 5537441A0E4431B600B1011D /* UKDiffParser.m */; }; 13 | 553744DF0E44637F00B1011D /* UKDiffView.m in Sources */ = {isa = PBXBuildFile; fileRef = 553744DE0E44637F00B1011D /* UKDiffView.m */; }; 14 | 553748F80E48E38200B1011D /* UKObjectLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 553748F70E48E38200B1011D /* UKObjectLogger.m */; }; 15 | 55EA2B970E4E4E010057AFAF /* UKDiffViewChooseLeft.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 55EA2B950E4E4E010057AFAF /* UKDiffViewChooseLeft.pdf */; }; 16 | 55EA2B980E4E4E010057AFAF /* UKDiffViewChooseRight.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 55EA2B960E4E4E010057AFAF /* UKDiffViewChooseRight.pdf */; }; 17 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 18 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 19 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 24 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 25 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 26 | 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 27 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 28 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 30 | 32CA4F630368D1EE00C91783 /* AngelDiff_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AngelDiff_Prefix.pch; sourceTree = ""; }; 31 | 5508EC040E4E5E1A00740CF8 /* Testfile1.udiff */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Testfile1.udiff; sourceTree = ""; }; 32 | 553744130E4430ED00B1011D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 33 | 553744140E4430ED00B1011D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 34 | 553744190E4431B600B1011D /* UKDiffParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UKDiffParser.h; sourceTree = ""; }; 35 | 5537441A0E4431B600B1011D /* UKDiffParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UKDiffParser.m; sourceTree = ""; }; 36 | 5537441F0E443B8900B1011D /* UKHelperMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UKHelperMacros.h; path = ../UliKit/UKHelperMacros.h; sourceTree = SOURCE_ROOT; }; 37 | 553744DD0E44637F00B1011D /* UKDiffView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UKDiffView.h; sourceTree = ""; }; 38 | 553744DE0E44637F00B1011D /* UKDiffView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UKDiffView.m; sourceTree = ""; }; 39 | 5537457C0E4471E600B1011D /* Testfile1.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Testfile1.txt; sourceTree = ""; }; 40 | 5537457D0E4471EE00B1011D /* Testfile2.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Testfile2.txt; sourceTree = ""; }; 41 | 5537457E0E44720000B1011D /* Testfile1.diff */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Testfile1.diff; sourceTree = ""; }; 42 | 553748F60E48E38200B1011D /* UKObjectLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UKObjectLogger.h; sourceTree = ""; }; 43 | 553748F70E48E38200B1011D /* UKObjectLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UKObjectLogger.m; sourceTree = ""; }; 44 | 55EA2B950E4E4E010057AFAF /* UKDiffViewChooseLeft.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = UKDiffViewChooseLeft.pdf; sourceTree = ""; }; 45 | 55EA2B960E4E4E010057AFAF /* UKDiffViewChooseRight.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = UKDiffViewChooseRight.pdf; sourceTree = ""; }; 46 | 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 8D1107320486CEB800E47090 /* AngelDiff.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AngelDiff.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 080E96DDFE201D6D7F000001 /* Classes */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 553744130E4430ED00B1011D /* AppDelegate.h */, 66 | 553744140E4430ED00B1011D /* AppDelegate.m */, 67 | 553744DD0E44637F00B1011D /* UKDiffView.h */, 68 | 553744DE0E44637F00B1011D /* UKDiffView.m */, 69 | 553744190E4431B600B1011D /* UKDiffParser.h */, 70 | 5537441A0E4431B600B1011D /* UKDiffParser.m */, 71 | ); 72 | name = Classes; 73 | sourceTree = ""; 74 | }; 75 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 79 | ); 80 | name = "Linked Frameworks"; 81 | sourceTree = ""; 82 | }; 83 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 87 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 88 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 89 | ); 90 | name = "Other Frameworks"; 91 | sourceTree = ""; 92 | }; 93 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 8D1107320486CEB800E47090 /* AngelDiff.app */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 29B97314FDCFA39411CA2CEA /* AngelDiff */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 5537457C0E4471E600B1011D /* Testfile1.txt */, 105 | 5537457E0E44720000B1011D /* Testfile1.diff */, 106 | 5508EC040E4E5E1A00740CF8 /* Testfile1.udiff */, 107 | 5537457D0E4471EE00B1011D /* Testfile2.txt */, 108 | 080E96DDFE201D6D7F000001 /* Classes */, 109 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 110 | 29B97317FDCFA39411CA2CEA /* Resources */, 111 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 112 | 19C28FACFE9D520D11CA2CBB /* Products */, 113 | ); 114 | name = AngelDiff; 115 | sourceTree = ""; 116 | }; 117 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 553748F60E48E38200B1011D /* UKObjectLogger.h */, 121 | 553748F70E48E38200B1011D /* UKObjectLogger.m */, 122 | 32CA4F630368D1EE00C91783 /* AngelDiff_Prefix.pch */, 123 | 29B97316FDCFA39411CA2CEA /* main.m */, 124 | 5537441F0E443B8900B1011D /* UKHelperMacros.h */, 125 | ); 126 | name = "Other Sources"; 127 | sourceTree = ""; 128 | }; 129 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 8D1107310486CEB800E47090 /* Info.plist */, 133 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 134 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, 135 | 55EA2B950E4E4E010057AFAF /* UKDiffViewChooseLeft.pdf */, 136 | 55EA2B960E4E4E010057AFAF /* UKDiffViewChooseRight.pdf */, 137 | ); 138 | name = Resources; 139 | sourceTree = ""; 140 | }; 141 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 145 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 146 | ); 147 | name = Frameworks; 148 | sourceTree = ""; 149 | }; 150 | /* End PBXGroup section */ 151 | 152 | /* Begin PBXNativeTarget section */ 153 | 8D1107260486CEB800E47090 /* AngelDiff */ = { 154 | isa = PBXNativeTarget; 155 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "AngelDiff" */; 156 | buildPhases = ( 157 | 8D1107290486CEB800E47090 /* Resources */, 158 | 8D11072C0486CEB800E47090 /* Sources */, 159 | 8D11072E0486CEB800E47090 /* Frameworks */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | ); 165 | name = AngelDiff; 166 | productInstallPath = "$(HOME)/Applications"; 167 | productName = AngelDiff; 168 | productReference = 8D1107320486CEB800E47090 /* AngelDiff.app */; 169 | productType = "com.apple.product-type.application"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 175 | isa = PBXProject; 176 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AngelDiff" */; 177 | compatibilityVersion = "Xcode 3.1"; 178 | hasScannedForEncodings = 1; 179 | mainGroup = 29B97314FDCFA39411CA2CEA /* AngelDiff */; 180 | projectDirPath = ""; 181 | projectRoot = ""; 182 | targets = ( 183 | 8D1107260486CEB800E47090 /* AngelDiff */, 184 | ); 185 | }; 186 | /* End PBXProject section */ 187 | 188 | /* Begin PBXResourcesBuildPhase section */ 189 | 8D1107290486CEB800E47090 /* Resources */ = { 190 | isa = PBXResourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 194 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, 195 | 55EA2B970E4E4E010057AFAF /* UKDiffViewChooseLeft.pdf in Resources */, 196 | 55EA2B980E4E4E010057AFAF /* UKDiffViewChooseRight.pdf in Resources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXResourcesBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 8D11072C0486CEB800E47090 /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 208 | 553744150E4430ED00B1011D /* AppDelegate.m in Sources */, 209 | 5537441B0E4431B600B1011D /* UKDiffParser.m in Sources */, 210 | 553744DF0E44637F00B1011D /* UKDiffView.m in Sources */, 211 | 553748F80E48E38200B1011D /* UKObjectLogger.m in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 089C165DFE840E0CC02AAC07 /* English */, 222 | ); 223 | name = InfoPlist.strings; 224 | sourceTree = ""; 225 | }; 226 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | 1DDD58150DA1D0A300B32029 /* English */, 230 | ); 231 | name = MainMenu.xib; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | C01FCF4B08A954540054247B /* Debug */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | COPY_PHASE_STRIP = NO; 242 | GCC_DYNAMIC_NO_PIC = NO; 243 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 244 | GCC_MODEL_TUNING = G5; 245 | GCC_OPTIMIZATION_LEVEL = 0; 246 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 247 | GCC_PREFIX_HEADER = AngelDiff_Prefix.pch; 248 | GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 251 | GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; 252 | GCC_WARN_EFFECTIVE_CPLUSPLUS_VIOLATIONS = YES; 253 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 254 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 255 | GCC_WARN_MISSING_PARENTHESES = YES; 256 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; 257 | GCC_WARN_SHADOW = YES; 258 | GCC_WARN_SIGN_COMPARE = YES; 259 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; 260 | GCC_WARN_UNUSED_PARAMETER = YES; 261 | INFOPLIST_FILE = Info.plist; 262 | INSTALL_PATH = "$(HOME)/Applications"; 263 | PRODUCT_NAME = AngelDiff; 264 | }; 265 | name = Debug; 266 | }; 267 | C01FCF4C08A954540054247B /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | GCC_MODEL_TUNING = G5; 273 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 274 | GCC_PREFIX_HEADER = AngelDiff_Prefix.pch; 275 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 276 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 277 | GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; 278 | GCC_WARN_EFFECTIVE_CPLUSPLUS_VIOLATIONS = YES; 279 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 280 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 281 | GCC_WARN_MISSING_PARENTHESES = YES; 282 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; 283 | GCC_WARN_SHADOW = YES; 284 | GCC_WARN_SIGN_COMPARE = YES; 285 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; 286 | GCC_WARN_UNUSED_PARAMETER = YES; 287 | INFOPLIST_FILE = Info.plist; 288 | INSTALL_PATH = "$(HOME)/Applications"; 289 | PRODUCT_NAME = AngelDiff; 290 | }; 291 | name = Release; 292 | }; 293 | C01FCF4F08A954540054247B /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 297 | GCC_C_LANGUAGE_STANDARD = c99; 298 | GCC_OPTIMIZATION_LEVEL = 0; 299 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | ONLY_ACTIVE_ARCH = YES; 302 | PREBINDING = NO; 303 | SDKROOT = macosx10.5; 304 | }; 305 | name = Debug; 306 | }; 307 | C01FCF5008A954540054247B /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 311 | GCC_C_LANGUAGE_STANDARD = c99; 312 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 313 | GCC_WARN_UNUSED_VARIABLE = YES; 314 | PREBINDING = NO; 315 | SDKROOT = macosx10.5; 316 | }; 317 | name = Release; 318 | }; 319 | /* End XCBuildConfiguration section */ 320 | 321 | /* Begin XCConfigurationList section */ 322 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "AngelDiff" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | C01FCF4B08A954540054247B /* Debug */, 326 | C01FCF4C08A954540054247B /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AngelDiff" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | C01FCF4F08A954540054247B /* Debug */, 335 | C01FCF5008A954540054247B /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | /* End XCConfigurationList section */ 341 | }; 342 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 343 | } 344 | -------------------------------------------------------------------------------- /AngelDiff_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'AngelDiff' target in the 'AngelDiff' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 02.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | #import 29 | 30 | 31 | @class UKDiffView; 32 | 33 | 34 | @interface AppDelegate : NSObject 35 | { 36 | IBOutlet UKDiffView* diffView; 37 | NSString* currPath; 38 | } 39 | 40 | @property (retain) NSString* currPath; 41 | 42 | -(void) exportMerged: (id)sender; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 02.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | #import "AppDelegate.h" 29 | #import "UKDiffParser.h" 30 | #import "UKDiffView.h" 31 | #import "UKHelperMacros.h" 32 | 33 | 34 | @implementation AppDelegate 35 | 36 | @synthesize currPath; 37 | 38 | -(void) dealloc 39 | { 40 | DESTROY(currPath); 41 | 42 | [super dealloc]; 43 | } 44 | 45 | - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename 46 | { 47 | #pragma unused(sender) 48 | self.currPath = [[filename stringByDeletingPathExtension] stringByAppendingPathExtension: @"txt"]; 49 | NSString* stringOne = [NSString stringWithContentsOfFile: self.currPath]; 50 | NSString* diffPath = [[filename stringByDeletingPathExtension] stringByAppendingPathExtension: @"udiff"]; 51 | BOOL uniDiff = YES; 52 | if( ![[NSFileManager defaultManager] fileExistsAtPath: diffPath] ) 53 | { 54 | diffPath = [[filename stringByDeletingPathExtension] stringByAppendingPathExtension: @"diff"]; 55 | uniDiff = NO; 56 | } 57 | NSString* differences = [NSString stringWithContentsOfFile: diffPath]; 58 | 59 | UKDiffParser* dp = nil; 60 | if( uniDiff ) 61 | dp = [[[UKDiffParser alloc] initWithUnifiedDiffText: differences] autorelease]; 62 | else 63 | dp = [[[UKDiffParser alloc] initWithDiffText: differences] autorelease]; 64 | [dp applyOriginalText: stringOne]; 65 | 66 | [diffView setDiffParser: dp]; 67 | NSRect box = [diffView frame]; 68 | box.size = [diffView bestSize]; 69 | [diffView setFrame: box]; 70 | [diffView setNeedsDisplay: YES]; 71 | 72 | return YES; 73 | } 74 | 75 | 76 | -(void) exportMerged: (id)sender 77 | { 78 | #pragma unused(sender) 79 | 80 | NSString* merged = [[diffView diffParser] mergedString]; 81 | [merged writeToFile: [[currPath stringByDeletingPathExtension] stringByAppendingPathExtension: @"merged.txt"] atomically: NO encoding: NSUTF8StringEncoding error: nil]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uliwitness/UKDiffView/15771d38e4ade8853606e97f88fe25579ea3dd72/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeName 11 | MyDocumentType 12 | CFBundleTypeOSTypes 13 | 14 | **** 15 | 16 | CFBundleTypeRole 17 | Editor 18 | LSTypeIsPackage 19 | 20 | NSPersistentStoreTypeKey 21 | XML 22 | 23 | 24 | CFBundleExecutable 25 | ${EXECUTABLE_NAME} 26 | CFBundleIconFile 27 | 28 | CFBundleIdentifier 29 | com.thevoidsoftware.${PRODUCT_NAME:identifier} 30 | CFBundleInfoDictionaryVersion 31 | 6.0 32 | CFBundleName 33 | ${PRODUCT_NAME} 34 | CFBundlePackageType 35 | APPL 36 | CFBundleSignature 37 | ???? 38 | CFBundleVersion 39 | 1.0 40 | NSMainNibFile 41 | MainMenu 42 | NSPrincipalClass 43 | NSApplication 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | UKDIFFVIEW 2 | ----------- 3 | 4 | A simple NSView subclass that takes an original file and a text-form diff and displays it in a graphical form, and can selectively apply the modifications it contains. 5 | 6 | This currently handles regular diffs, like: 7 | 8 | 6c6 9 | < foo 10 | --- 11 | > bar 12 | 13 | and diffs in unified diff format, like: 14 | 15 | --- /path/to/file1.txt 2004-01-03 00:12:34.000000000 +0100 16 | +++ /path/to/file2.txt 2008-08-02 12:34:56.000000000 +0200 17 | @@ -3,7 +3,7 @@ 18 | context 19 | context 20 | context 21 | - foo 22 | + bar 23 | context 24 | context 25 | 26 | 27 | IMPORTANT 28 | --------- 29 | 30 | This project uses the UliKit. You *must* check out UliKit into a folder next to the UKDiffView folder to have this project compile. 31 | 32 | 33 | LICENSE 34 | ------- 35 | 36 | Copyright 2008 M. Uli Kusterer. 37 | 38 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 39 | 40 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 41 | 42 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 43 | 44 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 45 | 46 | 3. This notice may not be removed or altered from any source distribution. 47 | -------------------------------------------------------------------------------- /Testfile1.diff: -------------------------------------------------------------------------------- 1 | 6c6 2 | < * Copyright (c) 2003 M. Uli Kusterer. All rights reserved. 3 | --- 4 | > * Copyright (c) 2009 Gerda the Great. All rights reserved. 5 | 294,309d293 6 | < protected: 7 | < BCTokenList mTokens; // List of tokens (after Tokenize() has been called). 8 | < BCInstructionList mCode; // Code (after Parse() has been called). 9 | < char* mText; // Text being tokenized/parsed (only until parsing is complete) 10 | < bool mCompileForDebug; // Generate instructions that aid in source-level debugging? 11 | < std::string mCurrentFilename; // Name of current source file. 12 | < BCConstantStringsMap mConstantStrings; // String constant -> String instruction index mappings used by AddStringInstruction to allow re-use. 13 | < static BCIdentifierIDMap sIdentifiers; // List of identifiers (after construction). 14 | < static BCOperatorInstrMap sOperatorInstrs; // Mappings from operator token ID to instruction type. 15 | < static BCOpPrecedenceMap sOpPrecedences; // Mappings from operator instruction type to operator precedence (higher means gets the left argument). 16 | < static BCSystemFunctionMap sSystemFunctions; // Mappings from system function name to signature. 17 | < static BCClassMap sClasses; // Information about the different classes we've seen so far. 18 | < static BCUInt32 sObjectIDSeed; // Number for a new object's ID. If you use it, add one to it so the next user gets a fresh one. 19 | < static BCIntegerConstMap sIntegralConstants; // Mappings from token type for a constant to its integer value. 20 | < static BCStringConstMap sStringConstants; // Mappings from token type for a constant to its integral value. 21 | < 22 | 348a333,334 23 | > void TokenizeOneStringChar( size_t x, std::string& vCurrTokenStr, unsigned *ioTokenState, 24 | > BCToken* vCurrToken ); 25 | 359c345 26 | < void TokenizeOneNumberChar( size_t x, std::string& vCurrTokenStr, 27 | --- 28 | > void TokenizeOneNumberChar( size_t x, std::deque& vCurrTokenStr, 29 | 362c348 30 | < void TokenizeOneCommentChar( size_t x, std::string& vCurrTokenStr, 31 | --- 32 | > void TokenizeOneCommentChar( size_t x, std::deque& vCurrTokenStr, 33 | 365c351,352 34 | < void TokenizeOneBlockCommentChar( size_t x, std::string& vCurrTokenStr, 35 | --- 36 | > //additional line for testing. 37 | > void TokenizeOneBlockCommentChar( size_t x, std::deque& vCurrTokenStr, 38 | 369,370d355 39 | < void TokenizeOneStringChar( size_t x, std::string& vCurrTokenStr, unsigned *ioTokenState, 40 | < BCToken* vCurrToken ); 41 | -------------------------------------------------------------------------------- /Testfile1.merged.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * BCParser.h 3 | * 1FACH 4 | * 5 | * Created by Uli Kusterer on Sun Jul 20 2003. 6 | * Copyright (c) 2009 Gerda the Great. All rights reserved. 7 | * 8 | */ 9 | 10 | /* ----------------------------------------------------------------------------- 11 | Headers: 12 | -------------------------------------------------------------------------- */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "BCInstruction.h" 19 | 20 | 21 | #ifndef BCPARSER_DEBUG 22 | #define BCPARSER_DEBUG 0 23 | #endif 24 | 25 | #ifndef BCDEBUG_TOKENIZER 26 | #define BCDEBUG_TOKENIZER BCPARSER_DEBUG 27 | #endif 28 | 29 | #if BCPARSER_DEBUG 30 | #define DEBUG_OUTPUT(n) printf("%s",n) 31 | #define DEBUG_OUTPUT2(n,o) printf(n,o) 32 | #define DEBUG_OUTPUT3(n,o,p) printf(n,o,p) 33 | #define DEBUG_OUTPUT4(n,o,p,q) printf(n,o,p,q) 34 | #else 35 | #define DEBUG_OUTPUT(n) // (n) 36 | #define DEBUG_OUTPUT2(n,o) // (n,o) 37 | #define DEBUG_OUTPUT3(n,o,p) // (n,o,p) 38 | #define DEBUG_OUTPUT4(n,o,p,q) // (n,o,p,q) 39 | #endif 40 | 41 | 42 | /* ----------------------------------------------------------------------------- 43 | BCToken: 44 | Constants and struct definition for the data structure used for keeping 45 | a list of tokens after tokenizing. 46 | -------------------------------------------------------------------------- */ 47 | 48 | // Possible values for mTokenType/during tokenizing: 49 | enum BCTokenTypeEnum 50 | { 51 | BCT_WHITESPACE = 0, // We're currently scanning whitespace. There should never be a token that has this type in BCParser::mTokens. This may be used as an indicator for end-of-list. 52 | BCT_NEWLINE, // A line break of any kind. 53 | BCT_IDENTIFIER, // Any kind of identifier, operator etc. 54 | BCT_STRING, // A double-quoted string constant. 55 | BCT_INTEGER, // An identifier consisting only of numerical digits. 56 | BCT_DOUBLE, // An identifier consisting only of numerical digits and a period. 57 | }; 58 | 59 | 60 | // More readable variant of BCT_WHITESPACE when used as end-of-list indicator: 61 | #define BCT_INVALID BCT_WHITESPACE 62 | 63 | 64 | // Possible values for mTokenID: 65 | // If you add something here, you must also add it to mIdentifiers in BCParser's constructor. 66 | enum BCTokenIDEnum 67 | { 68 | BCTI_ARBITRARY = 0, // Not a specially-recognized token. 69 | BCTI_ON, // "on" for message handlers. 70 | BCTI_END, // "end" for indicating end if, end handler etc. 71 | BCTI_IF, // "if" ... then ... 72 | BCTI_THEN, // if ... "then" ... 73 | BCTI_ELSE, // if ... then ... "else" 74 | BCTI_PUT, // "put" ... into ... 75 | BCTI_INTO, // put ... "into" ... 76 | BCTI_PASS, // "pass" through ... 77 | BCTI_THROUGH, // pass "through" ... 78 | BCTI_OPERATOR_PLUS, // "+" 79 | BCTI_OPERATOR_MINUS, // "-" 80 | BCTI_OPERATOR_MULTIPLY, // "*" 81 | BCTI_OPERATOR_DIVIDE, // "/" 82 | BCTI_OPERATOR_LIST_START, // "[" 83 | BCTI_OPERATOR_LIST_END, // "]" 84 | BCTI_OPERATOR_IS, // "is" or "=" 85 | BCTI_OPERATOR_COMMA, // "," 86 | BCTI_OPERATOR_AMPERSAND, // "&" 87 | BCTI_WITH, // "with" 88 | BCTI_OPERATOR_APOSTROPHE, // "'" for "object's property" 89 | BCTI_OPERATOR_OPEN_BRACKET, // "(" 90 | BCTI_OPERATOR_CLOSE_BRACKET, // ")" 91 | BCTI_PROPERTY, // "property" or "properties" 92 | BCTI_OPERATOR_MOD, // "mod" for remainder of division operator. 93 | BCTI_SYSTEM, // "system" identifier for system-native API calls. 94 | BCTI_S, // "s" for "object's property" 95 | BCTI_THE, // "the" for "the result" and such stuff 96 | BCTI_RESULT, // "result" for "the result" 97 | BCTI_NEW, // "new" as in "new function xy" [FFI] 98 | BCTI_FUNCTION, // "function" as in "new function xy" [FFI] 99 | BCTI_IN, // "in" as in "new function xy in " [FFI] 100 | BCTI_RETURNS, // "in" as in "new function xy returns type" [FFI] 101 | BCTI_NOTHING, // "nothing" as in "new function xy returns nothing" for void functions [FFI] 102 | BCTI_INTEGER, // "integer" as in "new function xy( integer )" for short integers [FFI] 103 | BCTI_CHARACTER, // "character" data type [FFI] 104 | BCTI_POINTER, // "pointer" data type [FFI] 105 | BCTI_PASCAL, // "pascal" data type qualifier [FFI] 106 | BCTI_STRING, // "string" data type [FFI] 107 | BCTI_FOUNDATION, // "foundation" data type qualifier for Macs [FFI] 108 | BCTI_UNSIGNED, // "unsigned" data type qualifier [FFI] 109 | BCTI_DECIMAL, // "decimal" data type qualifier [FFI] 110 | BCTI_NUMBER, // "number" data type [FFI] 111 | BCTI_BIG, // "big" data type qualifier [FFI] 112 | BCTI_OPERATOR_NOT, // binary "is not" operator or unary "not". 113 | BCTI_OPERATOR_IS_NOT, // binary single-character "is not" operator ("=/="), and token type returned for "is not" by ParseBinaryOperator. 114 | BCTI_CONTAINER, // "container" qualifier for pass-by-reference of parameters. 115 | BCTI_AS, // "as" qualifier indicating name for associative array item. 116 | BCTI_REPEAT, // "repeat" identifier used in loops. 117 | BCTI_WHILE, // "while" qualifier used in repeat loops. 118 | BCTI_TRUE, // "true" constant (1). 119 | BCTI_FALSE, // "false" constant (0). 120 | BCTI_RETURN, // "return" constant (ASCII character 13) or "return" command. 121 | BCTI_LINEFEED, // "linefeed" constant (ASCII character 10). 122 | BCTI_NULL, // "null" constant (ASCII character 0). 123 | BCTI_TAB, // "tab" constant (ASCII character 9). 124 | BCTI_OPERATOR_GREATER_THAN, // ">" operator 125 | BCTI_OPERATOR_LESS_THAN, // "<" operator 126 | }; 127 | 128 | 129 | // Character constants (may be platform-specific): 130 | 131 | // Unicode 0x0226: 132 | #define BCC_IS_NOT_SIGN_STR "≠" 133 | #define BCC_IS_NOT_SIGN_CHAR '≠' 134 | 135 | // Possible return values for ParseValue: 136 | enum 137 | { 138 | BCV_NONE, // Nothing eligible for a value found. 139 | BCV_STRING, // String constant parsed. 140 | BCV_INTEGER, // Integer constant parsed. 141 | BCV_DOUBLE, // Decimal number constant parsed. 142 | BCV_LIST, // Constant list of values parsed. 143 | BCV_OBJECT, // Object descriptor parsed. 144 | BCV_DYNAMIC, // Any non-constant value, like a function result or list entry. 145 | }; 146 | 147 | 148 | /* This is a data structure which we use as a shorthand notation for a token 149 | (a "word"). It may seem silly to keep info about a word of four characters 150 | in a 16-byte data structure, but since this one is fixed-size, scanning 151 | forward and backward is a lot faster, and comparisons usually only involve 152 | comparing four bytes instead of an entire string. */ 153 | 154 | typedef unsigned int BCTokenType; 155 | typedef unsigned int BCTokenID; 156 | typedef unsigned int BCOffset; 157 | 158 | struct BCToken 159 | { 160 | BCTokenType mTokenType; // String, number, identifier? 161 | BCTokenID mTokenID; // Number to identify some tokens we recognize right away. 162 | BCOffset mStartOffs; // Offset to first char of token in string. 163 | BCOffset mEndOffs; // Offset to last char of token in string. 164 | }; 165 | 166 | 167 | // Masks for our tokenizer's state machine: 168 | enum BCTokenState 169 | { 170 | BC_TOKEN_STATE_WHITESPACE = 0, 171 | BC_TOKEN_STATE_IDENTIFIER = (1 << 0), 172 | BC_TOKEN_STATE_STRING = (1 << 1), 173 | BC_TOKEN_STATE_ESCAPE_SEQ = (1 << 2), // Only when BC_TOKEN_STATE_STRING is set. 174 | BC_TOKEN_STATE_INTEGER = (1 << 3), // Check this *and* BC_TOKEN_STATE_NUMBER. If NUMBER is set and this isn't, it means we already found a decimal point. 175 | BC_TOKEN_STATE_NUMBER = (1 << 4), 176 | BC_TOKEN_STATE_COMMENT = (1 << 5), 177 | BC_TOKEN_STATE_BLOCK_COMMENT = (1 << 6), 178 | }; 179 | 180 | 181 | /* ----------------------------------------------------------------------------- 182 | Data Types: 183 | -------------------------------------------------------------------------- */ 184 | 185 | class BCSysFcnEntry 186 | { 187 | public: 188 | std::string mFunctionSignature; // Function signature (param and return value types). 189 | std::string mFunctionNameLib; // Name of function and library. 190 | 191 | public: 192 | BCSysFcnEntry() {}; 193 | BCSysFcnEntry( const BCSysFcnEntry* sfe ) { mFunctionSignature.assign( sfe->mFunctionSignature ); mFunctionNameLib.assign( sfe->mFunctionNameLib ); }; 194 | BCSysFcnEntry( const BCSysFcnEntry& sfe ) { mFunctionSignature.assign( sfe.mFunctionSignature ); mFunctionNameLib.assign( sfe.mFunctionNameLib ); }; 195 | BCSysFcnEntry( const std::string& sig, const std::string& nm ) : mFunctionSignature(sig), mFunctionNameLib(nm) {}; 196 | BCSysFcnEntry( const char* sig, const char* nm ) : mFunctionSignature(sig), mFunctionNameLib(nm) {}; 197 | 198 | BCSysFcnEntry& operator =( const BCSysFcnEntry& e ) { mFunctionSignature.assign( e.mFunctionSignature ); mFunctionNameLib.assign( e.mFunctionNameLib ); return *this; }; 199 | //const BCSysFcnEntry& operator =( const BCSysFcnEntry& e ) { mFunctionSignature.assign( e.mFunctionSignature ); mFunctionNameLib.assign( e.mFunctionNameLib ); return *this; }; 200 | }; 201 | 202 | class BCClassEntry 203 | { 204 | public: 205 | std::string mClassName; // Name of this class. 206 | BCClassEntry* mSuperclass; // Superclass of this class, NULL if none. 207 | unsigned int mSuperclassCount; // Number of classes we inherit stuff from. 208 | std::map mInstanceVars; // Variable name --> slot offset mappings. 209 | 210 | public: 211 | BCClassEntry() {}; 212 | BCClassEntry( std::string nm, BCClassEntry* su ) : mClassName(nm), mSuperclass(su) { CalcSuperclassCount(); }; 213 | BCClassEntry( const BCClassEntry* tm ) { mClassName.assign( tm->mClassName ); mSuperclass = tm->mSuperclass; mSuperclassCount = tm->mSuperclassCount; mInstanceVars = tm->mInstanceVars; }; 214 | BCClassEntry( const BCClassEntry& tm ) { mClassName.assign( tm.mClassName ); mSuperclass = tm.mSuperclass; mSuperclassCount = tm.mSuperclassCount; mInstanceVars = tm.mInstanceVars; }; 215 | 216 | protected: 217 | void CalcSuperclassCount() { if( !mSuperclass ) { mSuperclassCount = 0; return; } mSuperclassCount = mSuperclass->mSuperclassCount +1; }; 218 | }; 219 | 220 | typedef std::deque BCTokenList; // A list of BCToken objects. 221 | typedef std::deque BCInstructionList; // A list of BCInstruction structs. 222 | typedef std::map BCIdentifierIDMap; // Mapping from identifier names to their token IDs. Used by the tokenizer. 223 | typedef std::map BCOperatorInstrMap; // Mapping from a certain operator's token ID to the associated instruction ID. Note that some operators may actually consist of several tokens, in which case the token ID is a "virtual" ID that represents *both* tokens or an equivalent token's ID. 224 | typedef std::map BCOpPrecedenceMap; // Mapping from a particular operator's instruction ID to an operator precedence value, to decide which operation in an expression will be executed first. 225 | typedef std::map BCSystemFunctionMap; // Mapping from system function name to its signature. 226 | typedef std::map BCClassMap; // Mapping from class names to type information. 227 | typedef std::map BCVarNameToIndexMap; // Mapping from local var to its stack index. 228 | typedef std::map BCConstantStringsMap; // Look-up table that allows re-using string constant instructions. AddStringInstruction() adds all strings it creates to this, and whenever a string is requested it returns an existing entry from this table, where possible. 229 | typedef std::map BCIntegerConstMap; // Register all integral constants with this. 230 | typedef std::map BCStringConstMap; // Register all built-in string or character constants with this. 231 | 232 | 233 | /* ----------------------------------------------------------------------------- 234 | Exceptions: 235 | These are a couple of exception objects that are thrown when an error 236 | occurs. 237 | -------------------------------------------------------------------------- */ 238 | 239 | // Couldn't create a file: 240 | class BCCreateFileError : public std::runtime_error 241 | { 242 | public: 243 | BCCreateFileError( const std::string name ) : std::runtime_error(name) {}; 244 | 245 | virtual const char* what() const throw() 246 | { 247 | static char vMsg[512]; 248 | 249 | snprintf( vMsg, sizeof(vMsg), "Can't create file \"%s\".", std::runtime_error::what() ); 250 | 251 | return vMsg; 252 | }; 253 | }; 254 | 255 | 256 | // Couldn't write to a file: 257 | class BCWriteFileError : public std::runtime_error 258 | { 259 | public: 260 | BCWriteFileError( const std::string name ) : std::runtime_error(name) {}; 261 | }; 262 | 263 | 264 | // Syntax error or something like that in the parser: 265 | class BCParserError : public std::runtime_error 266 | { 267 | unsigned int mOffset; // Offset into the text where the error occurred. 268 | 269 | public: 270 | BCParserError( const std::string name, unsigned int offs = 0 ) : std::runtime_error(name) { mOffset = offs; }; 271 | 272 | virtual const char* what() const throw() 273 | { 274 | static char vMsg[512]; 275 | 276 | snprintf( vMsg, sizeof(vMsg), "%s (offset %u).", std::runtime_error::what(), mOffset ); 277 | 278 | return vMsg; 279 | }; 280 | }; 281 | 282 | 283 | /* ----------------------------------------------------------------------------- 284 | BCParser: 285 | The parser class. This is an object which can be used to turn text into 286 | instructions by tokenizing and parsing the text. There are also some 287 | methods that allow adding certain instructions to the code (e.g. if you 288 | just want to evaluate an expression and print its result to the 289 | console), and some methods that allow saving code to disk. 290 | -------------------------------------------------------------------------- */ 291 | 292 | class BCParser 293 | { 294 | protected: 295 | BCTokenList mTokens; // List of tokens (after Tokenize() has been called). 296 | BCInstructionList mCode; // Code (after Parse() has been called). 297 | char* mText; // Text being tokenized/parsed (only until parsing is complete) 298 | bool mCompileForDebug; // Generate instructions that aid in source-level debugging? 299 | std::string mCurrentFilename; // Name of current source file. 300 | BCConstantStringsMap mConstantStrings; // String constant -> String instruction index mappings used by AddStringInstruction to allow re-use. 301 | static BCIdentifierIDMap sIdentifiers; // List of identifiers (after construction). 302 | static BCOperatorInstrMap sOperatorInstrs; // Mappings from operator token ID to instruction type. 303 | static BCOpPrecedenceMap sOpPrecedences; // Mappings from operator instruction type to operator precedence (higher means gets the left argument). 304 | static BCSystemFunctionMap sSystemFunctions; // Mappings from system function name to signature. 305 | static BCClassMap sClasses; // Information about the different classes we've seen so far. 306 | static BCUInt32 sObjectIDSeed; // Number for a new object's ID. If you use it, add one to it so the next user gets a fresh one. 307 | static BCIntegerConstMap sIntegralConstants; // Mappings from token type for a constant to its integer value. 308 | static BCStringConstMap sStringConstants; // Mappings from token type for a constant to its integral value. 309 | 310 | public: 311 | BCParser(); 312 | 313 | void SetCurrentFilename( const std::string& s) { mCurrentFilename.assign( s ); }; // File path where the debugger should look for this source file. 314 | 315 | void Tokenize( const char* inText, size_t len ); // Call this first, and hand it your text to be parsed. The text needn't be zero-terminated, and length shouldn't include any terminating zero bytes if you add them. You mustn't dispose of the text until it's been parsed. 316 | void ClearTokens() { mTokens.clear(); }; 317 | 318 | void Parse(); // Call this or ParseMethodBody second. 319 | void ParseMethodBody( BCTokenList::iterator& itty, BCUInt32* numParams, BCVarNameToIndexMap& vNameToIndex, BCToken* stopTokens = NULL ); 320 | void ParseMethodBody( BCUInt32* numParams, BCVarNameToIndexMap& vNameToIndex ); 321 | void ParseHeaderFile(); 322 | 323 | void AddEndInstruction(); // Mark end of code block. 324 | void AddPrintResultInstructions(); // Adds a "print" instruction that outputs the variable "the result". Useful if you want to generate code using ParseMethodBody() that outputs the result instead of just leaving it on the stack to rot. 325 | void AddPrintInstruction(); 326 | 327 | void PrintAllTokens(); 328 | void PrintCode( std::ostream& outs ); // Prints the code in human-readable form to the specified stream. 329 | void SaveToFile( const char* fpath ); // Saves raw code (without instances). 330 | 331 | void SetCompileForDebug( bool n ) { mCompileForDebug = n; }; 332 | 333 | static void InitLookupTables(); // Called by the first BCParser's constructor automatically. 334 | 335 | protected: 336 | void EndToken( std::string& ioCurrTokenStr, BCToken* ioCurrToken, unsigned int inNewStart ); 337 | void ParseObject( BCTokenList::iterator& itty ); 338 | void ParseExpression( BCTokenList::iterator& itty, BCUInt32* numParams, BCVarNameToIndexMap& vNameToIndex ); 339 | unsigned int ParseBinaryOperator( BCTokenList::iterator& itty ); 340 | unsigned int ParseValue( BCTokenList::iterator& itty, BCUInt32* numParams, BCVarNameToIndexMap& vNameToIndex, bool needContainer = false ); 341 | bool ParseMethod( BCTokenList::iterator& itty ); 342 | BCUInt32* AddPushLocalVarsInstruction( int numVars ); 343 | BCUInt32 AddStringInstruction( std::string& str, bool addPushInstr = true ); 344 | void AddDebugInstructions( BCTokenList::iterator& itty ); 345 | void AppendInstruction( BCInstruction& instr ); 346 | char TokensToTypeChar( BCTokenList::iterator& itty ); 347 | void TokenizeOneWhitespaceChar( size_t x, std::string& vCurrTokenStr, unsigned *ioTokenState, 348 | BCToken* vCurrToken ); 349 | void TokenizeOneStringChar( size_t x, std::string& vCurrTokenStr, unsigned *ioTokenState, 350 | BCToken* vCurrToken ); 351 | bool IsOperatorChar( char c ); 352 | void TokenizeOneNewlineChar( size_t x, std::string& vCurrTokenStr, 353 | unsigned *ioTokenState, 354 | BCToken* vCurrToken ); 355 | void TokenizeOneOperatorChar( size_t x, std::string& vCurrTokenStr, 356 | unsigned *ioTokenState, 357 | BCToken* vCurrToken ); 358 | void TokenizeOneIdentifierChar( size_t x, std::string& vCurrTokenStr, 359 | unsigned *ioTokenState, 360 | BCToken* vCurrToken ); 361 | void TokenizeOneNumberChar( size_t x, std::deque& vCurrTokenStr, 362 | unsigned *ioTokenState, 363 | BCToken* vCurrToken ); 364 | void TokenizeOneCommentChar( size_t x, std::deque& vCurrTokenStr, 365 | unsigned *ioTokenState, 366 | BCToken* vCurrToken ); 367 | //additional line for testing. 368 | void TokenizeOneBlockCommentChar( size_t x, std::deque& vCurrTokenStr, 369 | unsigned *ioTokenState, 370 | BCToken* vCurrToken ); 371 | void AppendEscapedCharsFor( char escapeSequence, std::string& vCurrTokenStr ); 372 | }; -------------------------------------------------------------------------------- /Testfile1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uliwitness/UKDiffView/15771d38e4ade8853606e97f88fe25579ea3dd72/Testfile1.txt -------------------------------------------------------------------------------- /Testfile1.udiff: -------------------------------------------------------------------------------- 1 | --- /Users/uli/Programming/AngelDiff/Testfile1.txt 2004-01-03 00:19:05.000000000 +0100 2 | +++ /Users/uli/Programming/AngelDiff/Testfile2.txt 2008-08-02 12:34:16.000000000 +0200 3 | @@ -3,7 +3,7 @@ 4 | * 1FACH 5 | * 6 | * Created by Uli Kusterer on Sun Jul 20 2003. 7 | - * Copyright (c) 2003 M. Uli Kusterer. All rights reserved. 8 | + * Copyright (c) 2009 Gerda the Great. All rights reserved. 9 | * 10 | */ 11 | 12 | @@ -291,22 +291,6 @@ 13 | 14 | class BCParser 15 | { 16 | -protected: 17 | - BCTokenList mTokens; // List of tokens (after Tokenize() has been called). 18 | - BCInstructionList mCode; // Code (after Parse() has been called). 19 | - char* mText; // Text being tokenized/parsed (only until parsing is complete) 20 | - bool mCompileForDebug; // Generate instructions that aid in source-level debugging? 21 | - std::string mCurrentFilename; // Name of current source file. 22 | - BCConstantStringsMap mConstantStrings; // String constant -> String instruction index mappings used by AddStringInstruction to allow re-use. 23 | - static BCIdentifierIDMap sIdentifiers; // List of identifiers (after construction). 24 | - static BCOperatorInstrMap sOperatorInstrs; // Mappings from operator token ID to instruction type. 25 | - static BCOpPrecedenceMap sOpPrecedences; // Mappings from operator instruction type to operator precedence (higher means gets the left argument). 26 | - static BCSystemFunctionMap sSystemFunctions; // Mappings from system function name to signature. 27 | - static BCClassMap sClasses; // Information about the different classes we've seen so far. 28 | - static BCUInt32 sObjectIDSeed; // Number for a new object's ID. If you use it, add one to it so the next user gets a fresh one. 29 | - static BCIntegerConstMap sIntegralConstants; // Mappings from token type for a constant to its integer value. 30 | - static BCStringConstMap sStringConstants; // Mappings from token type for a constant to its integral value. 31 | - 32 | public: 33 | BCParser(); 34 | 35 | @@ -346,6 +330,8 @@ 36 | char TokensToTypeChar( BCTokenList::iterator& itty ); 37 | void TokenizeOneWhitespaceChar( size_t x, std::string& vCurrTokenStr, unsigned *ioTokenState, 38 | BCToken* vCurrToken ); 39 | + void TokenizeOneStringChar( size_t x, std::string& vCurrTokenStr, unsigned *ioTokenState, 40 | + BCToken* vCurrToken ); 41 | bool IsOperatorChar( char c ); 42 | void TokenizeOneNewlineChar( size_t x, std::string& vCurrTokenStr, 43 | unsigned *ioTokenState, 44 | @@ -356,16 +342,15 @@ 45 | void TokenizeOneIdentifierChar( size_t x, std::string& vCurrTokenStr, 46 | unsigned *ioTokenState, 47 | BCToken* vCurrToken ); 48 | - void TokenizeOneNumberChar( size_t x, std::string& vCurrTokenStr, 49 | + void TokenizeOneNumberChar( size_t x, std::deque& vCurrTokenStr, 50 | unsigned *ioTokenState, 51 | BCToken* vCurrToken ); 52 | - void TokenizeOneCommentChar( size_t x, std::string& vCurrTokenStr, 53 | + void TokenizeOneCommentChar( size_t x, std::deque& vCurrTokenStr, 54 | unsigned *ioTokenState, 55 | BCToken* vCurrToken ); 56 | - void TokenizeOneBlockCommentChar( size_t x, std::string& vCurrTokenStr, 57 | + //additional line for testing. 58 | + void TokenizeOneBlockCommentChar( size_t x, std::deque& vCurrTokenStr, 59 | unsigned *ioTokenState, 60 | BCToken* vCurrToken ); 61 | void AppendEscapedCharsFor( char escapeSequence, std::string& vCurrTokenStr ); 62 | - void TokenizeOneStringChar( size_t x, std::string& vCurrTokenStr, unsigned *ioTokenState, 63 | - BCToken* vCurrToken ); 64 | }; 65 | \ No newline at end of file 66 | -------------------------------------------------------------------------------- /Testfile2.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uliwitness/UKDiffView/15771d38e4ade8853606e97f88fe25579ea3dd72/Testfile2.txt -------------------------------------------------------------------------------- /UKDiffParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // UKDiffParser.h 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 02.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | // ----------------------------------------------------------------------------- 29 | // Headers: 30 | // ----------------------------------------------------------------------------- 31 | 32 | #import 33 | 34 | 35 | @class UKDiffEntry; 36 | 37 | // ----------------------------------------------------------------------------- 38 | // Constants: 39 | // ----------------------------------------------------------------------------- 40 | 41 | enum 42 | { 43 | UKDiffOperationChange = 'c', // newText replaces oldText. 44 | UKDiffOperationDelete = 'd', // oldText or nothing. 45 | UKDiffOperationAdd = 'a', // nothing or newText. 46 | UKDiffOperationUnchanged = 0 // oldText and newText are the same. destinationRange is unset. 47 | }; 48 | typedef unichar UKDiffOperation; 49 | 50 | 51 | // ----------------------------------------------------------------------------- 52 | // UKDiffParser: 53 | // Parses a diff in string form and can then turn a source text into the 54 | // destination text by applying this diff. 55 | // ----------------------------------------------------------------------------- 56 | 57 | @interface UKDiffParser : NSObject 58 | { 59 | NSMutableArray* differences; 60 | } 61 | 62 | @property (retain) NSMutableArray* differences; 63 | 64 | -(id) initWithDiffText: (NSString*)diffText; // The diff to apply. 65 | -(id) initWithUnifiedDiffText: (NSString*)diffText; // The diff to apply. 66 | 67 | -(void) applyOriginalText: (NSString*)origText; // The text to apply the diff to. 68 | 69 | -(NSString*) originalString; // Must have called applyOriginalText: once before using this. 70 | -(NSString*) destinationString; // Must have called applyOriginalText: once before using this. 71 | -(NSString*) mergedString; // Must have called applyOriginalText: once before using this. 72 | 73 | // Use these e.g for a view that displays a diff: 74 | -(NSUInteger) count; 75 | -(UKDiffEntry*) entryAtIndex: (NSUInteger)idx; 76 | 77 | // Going away: 78 | -(NSString*) stringByApplyingChangesToOriginalText: (NSString*)origText; 79 | 80 | @end 81 | 82 | 83 | // ----------------------------------------------------------------------------- 84 | // UKDiffEntry: 85 | // Class used for storing a parsed version of the source, destination and 86 | // their differences. 87 | // ----------------------------------------------------------------------------- 88 | 89 | @interface UKDiffEntry : NSObject 90 | { 91 | UKDiffOperation operation; 92 | NSRange originalRange; 93 | NSRange destinationRange; 94 | NSString* newText; 95 | NSString* oldText; 96 | BOOL apply; // YES if we should apply this change, NO if we should leave the original text in the output. 97 | } 98 | 99 | @property (assign) UKDiffOperation operation; 100 | @property (assign) NSRange originalRange; 101 | @property (assign) NSRange destinationRange; 102 | @property (retain) NSString* newText; 103 | @property (retain) NSString* oldText; 104 | @property (assign) BOOL apply; 105 | 106 | @end 107 | 108 | -------------------------------------------------------------------------------- /UKDiffParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // UKDiffParser.m 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 02.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | #import "UKDiffParser.h" 29 | #import "UKHelperMacros.h" 30 | 31 | 32 | @implementation UKDiffParser 33 | 34 | @synthesize differences; 35 | 36 | -(id) initWithDiffText: (NSString*)diffText 37 | { 38 | if(( self = [super init] )) 39 | { 40 | self.differences = [NSMutableArray array]; 41 | NSUInteger currPos = 0, endPos = [diffText length]; 42 | while( currPos < endPos ) 43 | { 44 | unichar currCh = [diffText characterAtIndex: currPos]; 45 | if( currCh == '\n' || currCh == '\r' ) // Empty line? Skip. 46 | { 47 | currPos++; 48 | continue; 49 | } 50 | 51 | // Get info about operation: 52 | NSUInteger num[4] = { 0, 0, 0, 0 }, 53 | numIdx = 0; 54 | unichar separators[4] = { 0, 0, 0, 0 }; 55 | NSMutableString* currStr = [NSMutableString string]; 56 | while( currPos < endPos ) 57 | { 58 | currCh = [diffText characterAtIndex: currPos++]; 59 | if( isnumber( currCh ) ) 60 | [currStr appendFormat: @"%C", currCh]; 61 | else 62 | { 63 | num[numIdx] = [currStr integerValue]; 64 | separators[numIdx] = currCh; 65 | numIdx++; 66 | [currStr deleteCharactersInRange: NSMakeRange( 0, [currStr length] )]; 67 | 68 | if( currCh == '\n' || currCh == '\r' ) 69 | break; 70 | } 71 | } 72 | 73 | NSRange originalRange, destRange; 74 | originalRange.location = num[0] -1; 75 | NSUInteger separatorIdx = 0; 76 | 77 | if( separators[0] == ',' ) 78 | { 79 | originalRange.length = num[1] -originalRange.location; 80 | separatorIdx++; 81 | } 82 | else 83 | originalRange.length = 1; 84 | 85 | destRange.location = num[separatorIdx+1] -1; 86 | if( separators[separatorIdx+1] == ',' ) 87 | destRange.length = num[separatorIdx+2] -destRange.location; 88 | else 89 | destRange.length = 1; 90 | 91 | // If it's a source line, ignore it for now: 92 | currCh = [diffText characterAtIndex: currPos]; 93 | while( currCh == '<' && currPos < endPos ) 94 | { 95 | do 96 | { 97 | currPos++; 98 | currCh = [diffText characterAtIndex: currPos]; 99 | } 100 | while( currCh != '\n' && currCh != '\r' && currPos < endPos ); 101 | if( currPos < (endPos-1) ) 102 | { 103 | currPos++; 104 | currCh = [diffText characterAtIndex: currPos]; 105 | } 106 | } 107 | if( currCh == '-' && currPos < endPos ) // Skip separator line. 108 | { 109 | do 110 | { 111 | currPos++; 112 | currCh = [diffText characterAtIndex: currPos]; 113 | } 114 | while( currCh != '\n' && currCh != '\r' && currPos < endPos ); 115 | if( currPos < (endPos-1) ) 116 | { 117 | currPos++; 118 | currCh = [diffText characterAtIndex: currPos]; 119 | } 120 | } 121 | 122 | NSMutableString* newString = [NSMutableString string]; 123 | while( currCh == '>' && currPos < endPos ) 124 | { 125 | currCh = [diffText characterAtIndex: ++currPos]; 126 | if( currCh != ' ' ) // There's a greater-than and a space, usually. 127 | currPos--; 128 | 129 | do 130 | { 131 | currPos++; 132 | currCh = [diffText characterAtIndex: currPos]; 133 | [newString appendFormat: @"%C", currCh]; 134 | } 135 | while( currCh != '\n' && currCh != '\r' && currPos < endPos ); 136 | if( currPos < (endPos-1) ) 137 | { 138 | currPos++; 139 | currCh = [diffText characterAtIndex: currPos]; 140 | } 141 | } 142 | 143 | UKDiffEntry* currDiff = [[[UKDiffEntry alloc] init] autorelease]; 144 | 145 | [currDiff setOperation: separators[separatorIdx]]; 146 | [currDiff setOriginalRange: originalRange]; 147 | [currDiff setDestinationRange: destRange]; 148 | [currDiff setNewText: newString]; 149 | [differences addObject: currDiff]; 150 | } 151 | } 152 | 153 | return self; 154 | } 155 | 156 | 157 | -(id) initWithUnifiedDiffText: (NSString*)diffText 158 | { 159 | if(( self = [super init] )) 160 | { 161 | self.differences = [NSMutableArray array]; 162 | NSUInteger currPos = 0, endPos = [diffText length]; 163 | while( currPos < endPos ) 164 | { 165 | UKLog(@"NEW ITERATION"); 166 | unichar currCh = [diffText characterAtIndex: currPos]; 167 | if( currCh == '-' || currCh == '+' ) 168 | { 169 | while( (currCh != '\n' && currCh != '\r') && currPos < endPos ) 170 | { 171 | currCh = [diffText characterAtIndex: ++currPos]; 172 | } 173 | UKLog(@"\tSkipping file marker line"); 174 | } 175 | 176 | if( currCh == '\n' || currCh == '\r' ) // Empty line? Skip. 177 | { 178 | UKLog(@"\tSkipping empty line"); 179 | ++currPos; 180 | continue; 181 | } 182 | 183 | // @@ 184 | if( currCh != '@' ) 185 | { 186 | [self autorelease]; 187 | UKLog(@"NO @@ Marker found"); 188 | return nil; 189 | } 190 | 191 | currCh = [diffText characterAtIndex: ++currPos]; 192 | if( currCh != '@' ) 193 | { 194 | [self autorelease]; 195 | UKLog(@"NO @@ Marker found"); 196 | return nil; 197 | } 198 | 199 | UKLog(@"\t@@ Markers found"); 200 | 201 | currCh = [diffText characterAtIndex: ++currPos]; 202 | while( currCh == ' ' || currCh == '\t' ) 203 | currCh = [diffText characterAtIndex: ++currPos]; 204 | 205 | // -nnn,nn 206 | if( currCh != '-' ) 207 | { 208 | [self autorelease]; 209 | UKLog(@"NO - found"); 210 | return nil; 211 | } 212 | 213 | NSRange leftRange; 214 | NSMutableString* numStr = [NSMutableString string]; 215 | while( currPos < endPos ) 216 | { 217 | currCh = [diffText characterAtIndex: ++currPos]; 218 | if( !isnumber( currCh ) ) 219 | break; 220 | [numStr appendFormat: @"%C", currCh]; 221 | } 222 | 223 | leftRange.location = [numStr integerValue] -1; 224 | UKLog(@"\tleftRange.location = %ld",leftRange.location); 225 | 226 | if( currCh != ',' ) 227 | { 228 | leftRange.length = 1; 229 | currPos++; 230 | } 231 | else 232 | { 233 | [numStr deleteCharactersInRange: NSMakeRange( 0, [numStr length] )]; 234 | while( currPos < endPos ) 235 | { 236 | currCh = [diffText characterAtIndex: ++currPos]; 237 | if( !isnumber( currCh ) ) 238 | break; 239 | [numStr appendFormat: @"%C", currCh]; 240 | } 241 | 242 | leftRange.length = [numStr integerValue]; 243 | } 244 | UKLog(@"\tleftRange.length = %ld",leftRange.length); 245 | 246 | while( currCh == ' ' || currCh == '\t' ) 247 | currCh = [diffText characterAtIndex: ++currPos]; 248 | 249 | // +nnn,nn 250 | if( currCh != '+' ) 251 | { 252 | [self autorelease]; 253 | UKLog(@"\tNO + found"); 254 | return nil; 255 | } 256 | 257 | NSRange rightRange; 258 | numStr = [NSMutableString string]; 259 | while( currPos < endPos ) 260 | { 261 | currCh = [diffText characterAtIndex: ++currPos]; 262 | if( !isnumber( currCh ) ) 263 | break; 264 | [numStr appendFormat: @"%C", currCh]; 265 | } 266 | 267 | rightRange.location = [numStr integerValue] -1; 268 | UKLog(@"\trightRange.location = %ld",rightRange.location); 269 | 270 | if( currCh != ',' ) 271 | { 272 | rightRange.length = 1; 273 | currPos++; 274 | } 275 | else 276 | { 277 | [numStr deleteCharactersInRange: NSMakeRange( 0, [numStr length] )]; 278 | while( currPos < endPos ) 279 | { 280 | currCh = [diffText characterAtIndex: ++currPos]; 281 | if( !isnumber( currCh ) ) 282 | break; 283 | [numStr appendFormat: @"%C", currCh]; 284 | } 285 | 286 | rightRange.length = [numStr integerValue]; 287 | } 288 | UKLog(@"\trightRange.length = %ld",rightRange.length); 289 | 290 | while( currCh == ' ' || currCh == '\t' ) 291 | currCh = [diffText characterAtIndex: ++currPos]; 292 | 293 | if( currCh != '@' ) 294 | { 295 | [self autorelease]; 296 | UKLog(@"NO ending @@"); 297 | return nil; 298 | } 299 | currCh = [diffText characterAtIndex: ++currPos]; 300 | if( currCh != '@' ) 301 | { 302 | [self autorelease]; 303 | UKLog(@"NO ending @@"); 304 | return nil; 305 | } 306 | 307 | currCh = [diffText characterAtIndex: ++currPos]; 308 | while( (currCh == '\n' || currCh == '\r') && currPos < (endPos-1) ) 309 | currCh = [diffText characterAtIndex: ++currPos]; 310 | 311 | if( leftRange.location == 355 ) 312 | NSLog(@"355"); 313 | 314 | while( (currCh == ' ' || currCh == '-' || currCh == '+') && currPos < (endPos-1) ) 315 | { 316 | leftRange.length = rightRange.length = 0; 317 | 318 | // Now skip those useless context lines: 319 | while( currCh == ' ' ) 320 | { 321 | leftRange.location ++; 322 | rightRange.location ++; 323 | while( (currCh != '\n' && currCh != '\r') && currPos < (endPos-1) ) 324 | currCh = [diffText characterAtIndex: ++currPos]; 325 | while( (currCh == '\n' || currCh == '\r') && currPos < (endPos-1) ) 326 | currCh = [diffText characterAtIndex: ++currPos]; 327 | UKLog(@"Skipping context line"); 328 | } 329 | 330 | // Obtain original lines: 331 | NSMutableString* leftString = [NSMutableString string]; 332 | while( currCh == '-' ) 333 | { 334 | currCh = [diffText characterAtIndex: ++currPos]; 335 | while( (currCh != '\n' && currCh != '\r') && currPos < (endPos-1) ) 336 | { 337 | [leftString appendFormat: @"%C",currCh]; 338 | currCh = [diffText characterAtIndex: ++currPos]; 339 | } 340 | while( (currCh == '\n' || currCh == '\r') && currPos < (endPos-1) ) 341 | { 342 | leftRange.length++; 343 | [leftString appendFormat: @"%C",currCh]; 344 | currCh = [diffText characterAtIndex: ++currPos]; 345 | } 346 | UKLog(@"Found - line"); 347 | } 348 | 349 | // Obtain new lines: 350 | NSMutableString* rightString = [NSMutableString string]; 351 | while( currCh == '+' ) 352 | { 353 | currCh = [diffText characterAtIndex: ++currPos]; 354 | while( (currCh != '\n' && currCh != '\r') && currPos < (endPos-1) ) 355 | { 356 | [rightString appendFormat: @"%C",currCh]; 357 | currCh = [diffText characterAtIndex: ++currPos]; 358 | } 359 | while( (currCh == '\n' || currCh == '\r') && currPos < (endPos-1) ) 360 | { 361 | rightRange.length++; 362 | [rightString appendFormat: @"%C",currCh]; 363 | currCh = [diffText characterAtIndex: ++currPos]; 364 | } 365 | UKLog(@"Found + line"); 366 | } 367 | 368 | UKDiffEntry* currDiff = [[[UKDiffEntry alloc] init] autorelease]; 369 | 370 | UKDiffOperation op = UKDiffOperationChange; 371 | if( [leftString length] == 0 ) 372 | op = UKDiffOperationAdd; 373 | else if( [rightString length] == 0 ) 374 | op = UKDiffOperationDelete; 375 | [currDiff setOperation: op]; 376 | [currDiff setOriginalRange: leftRange]; 377 | [currDiff setDestinationRange: rightRange]; 378 | [currDiff setNewText: rightString]; 379 | [currDiff setOldText: leftString]; 380 | [differences addObject: currDiff]; 381 | 382 | UKLog( @"%@", currDiff ); 383 | 384 | leftRange.location += leftRange.length; 385 | rightRange.location += rightRange.length; 386 | 387 | // Skip remaining context lines: 388 | while( currCh == ' ' || currCh == '\\' ) 389 | { 390 | leftRange.location ++; 391 | rightRange.location ++; 392 | while( (currCh != '\n' && currCh != '\r') && currPos < (endPos-1) ) 393 | currCh = [diffText characterAtIndex: ++currPos]; 394 | while( (currCh == '\n' || currCh == '\r') && currPos < (endPos-1) ) 395 | currCh = [diffText characterAtIndex: ++currPos]; 396 | UKLog(@"Skipped trailing context line"); 397 | } 398 | } 399 | } 400 | } 401 | 402 | UKLog(@"parsed"); 403 | 404 | return self; 405 | } 406 | 407 | 408 | -(void) dealloc 409 | { 410 | DESTROY(differences); 411 | 412 | [super dealloc]; 413 | } 414 | 415 | 416 | -(NSArray*) linesArrayForText: (NSString*)str 417 | { 418 | NSMutableArray* lines = [NSMutableArray array]; 419 | NSMutableString* currStr = [NSMutableString string]; 420 | NSUInteger x = 0, len = [str length]; 421 | for( x = 0; x < len; x++ ) 422 | { 423 | unichar currCh = [str characterAtIndex: x]; 424 | [currStr appendFormat: @"%C",currCh]; 425 | if( currCh == '\n' || currCh == '\r' ) 426 | { 427 | [lines addObject: currStr]; 428 | currStr = [NSMutableString string]; 429 | } 430 | } 431 | 432 | if( [currStr length] > 0 ) 433 | [lines addObject: currStr]; 434 | 435 | return lines; 436 | } 437 | 438 | 439 | -(NSString*) stringByApplyingChangesToOriginalText: (NSString*)origText 440 | { 441 | NSArray* originalLines = [self linesArrayForText: origText]; 442 | NSUInteger currOriginalLine = 0, maxOriginalLines = [originalLines count]; 443 | NSMutableString* outStr = [NSMutableString string]; 444 | 445 | for( UKDiffEntry* currDiff in differences ) 446 | { 447 | UKDiffOperation operation = [currDiff operation]; 448 | NSRange originalRange = [currDiff originalRange]; 449 | NSString* newString = [currDiff newText]; 450 | 451 | if( operation == UKDiffOperationUnchanged ) // Ignore those, we do that ourselves. 452 | continue; 453 | 454 | while( originalRange.location > currOriginalLine ) 455 | { 456 | NSString* theLine = [originalLines objectAtIndex: currOriginalLine++]; 457 | [outStr appendString: theLine]; 458 | } 459 | 460 | if( operation == UKDiffOperationChange ) 461 | { 462 | [outStr appendString: newString]; 463 | currOriginalLine += originalRange.length; 464 | } 465 | else if( operation == UKDiffOperationDelete ) 466 | { 467 | currOriginalLine += originalRange.length; 468 | } 469 | else if( operation == UKDiffOperationAdd ) 470 | { 471 | while( (originalRange.location +originalRange.length) > currOriginalLine ) 472 | { 473 | NSString* theLine = [originalLines objectAtIndex: currOriginalLine++]; 474 | [outStr appendString: theLine]; 475 | } 476 | [outStr appendString: newString]; 477 | } 478 | } 479 | 480 | while( maxOriginalLines > currOriginalLine ) 481 | [outStr appendString: [originalLines objectAtIndex: currOriginalLine++]]; 482 | 483 | return outStr; 484 | } 485 | 486 | 487 | -(void) applyOriginalText: (NSString*)origText 488 | { 489 | NSArray* originalLines = [self linesArrayForText: origText]; 490 | NSUInteger currOriginalLine = 0, maxOriginalLines = [originalLines count]; 491 | NSUInteger prevOriginalLine = 0; 492 | NSMutableArray* newDifferences = [NSMutableArray array]; 493 | 494 | if( [originalLines count] == 0 ) 495 | return; 496 | 497 | for( UKDiffEntry* currDiff in differences ) 498 | { 499 | NSRange originalRange = [currDiff originalRange]; 500 | UKDiffOperation operation = [currDiff operation]; 501 | 502 | if( operation == UKDiffOperationUnchanged ) // Drop any leftover unchanged entries from a previous run. 503 | continue; 504 | 505 | // Generate an entry for any unchanged text between this difference and the previous one: 506 | NSUInteger lastLineNeeded = originalRange.location; 507 | if( operation == UKDiffOperationAdd ) 508 | lastLineNeeded += originalRange.length; 509 | 510 | prevOriginalLine = currOriginalLine; 511 | NSMutableString* currStr = [NSMutableString string]; 512 | while( lastLineNeeded > currOriginalLine ) 513 | { 514 | NSString* theLine = [originalLines objectAtIndex: currOriginalLine++]; 515 | [currStr appendString: theLine]; 516 | } 517 | 518 | if( [currStr length] > 0 ) 519 | { 520 | UKDiffEntry* newDiff = [[[UKDiffEntry alloc] init] autorelease]; 521 | 522 | [newDiff setOperation: UKDiffOperationUnchanged]; 523 | [newDiff setOriginalRange: NSMakeRange(prevOriginalLine,currOriginalLine -prevOriginalLine)]; 524 | [newDiff setOldText: currStr]; 525 | [newDiff setNewText: currStr]; 526 | [newDifferences addObject: newDiff]; 527 | } 528 | 529 | // Now create an entry for the current change: 530 | if( operation == UKDiffOperationAdd ) 531 | [newDifferences addObject: currDiff]; // Add object is already done, doesn't need an 'oldText'. 532 | else // Change or delete: 533 | { 534 | // Capture old text and add it to this diff item: 535 | NSMutableString* replicatedString = [NSMutableString string]; 536 | while( (originalRange.location +originalRange.length) > currOriginalLine ) 537 | { 538 | NSString* theLine = [originalLines objectAtIndex: currOriginalLine++]; 539 | [replicatedString appendString: theLine]; 540 | } 541 | 542 | [currDiff setOldText: replicatedString]; 543 | [newDifferences addObject: currDiff]; 544 | } 545 | } 546 | 547 | // Append any remaining unchanged text at end of diffs: 548 | NSMutableString* finalStr = [NSMutableString string]; 549 | prevOriginalLine = currOriginalLine; 550 | while( maxOriginalLines > currOriginalLine ) 551 | [finalStr appendString: [originalLines objectAtIndex: currOriginalLine++]]; 552 | if( [finalStr length] > 0 ) 553 | { 554 | UKDiffEntry* newDiff = [[[UKDiffEntry alloc] init] autorelease]; 555 | 556 | [newDiff setOperation: UKDiffOperationUnchanged]; 557 | [newDiff setOriginalRange: NSMakeRange(prevOriginalLine,currOriginalLine -prevOriginalLine)]; 558 | [newDiff setOldText: finalStr]; 559 | [newDiff setNewText: finalStr]; 560 | [newDifferences addObject: newDiff]; 561 | } 562 | 563 | // Replace old differences array with new one containing both texts: 564 | self.differences = newDifferences; 565 | } 566 | 567 | 568 | -(NSString*) originalString // Must have called applyOriginalText: once before using this. 569 | { 570 | NSMutableString* outStr = [NSMutableString string]; 571 | 572 | for( UKDiffEntry* currDiff in differences ) 573 | { 574 | NSString* currStr = [currDiff oldText]; 575 | if( currStr ) 576 | [outStr appendString: currStr]; 577 | } 578 | 579 | return outStr; 580 | } 581 | 582 | 583 | -(NSString*) destinationString // Must have called applyOriginalText: once before using this. 584 | { 585 | NSMutableString* outStr = [NSMutableString string]; 586 | 587 | for( UKDiffEntry* currDiff in differences ) 588 | { 589 | NSString* currStr = [currDiff newText]; 590 | if( currStr ) 591 | [outStr appendString: currStr]; 592 | } 593 | 594 | return outStr; 595 | } 596 | 597 | 598 | -(NSString*) mergedString // Must have called applyOriginalText: once before using this. 599 | { 600 | NSMutableString* outStr = [NSMutableString string]; 601 | 602 | for( UKDiffEntry* currDiff in differences ) 603 | { 604 | NSString* currStr = [currDiff apply] ? [currDiff newText] : [currDiff oldText]; 605 | if( currStr ) 606 | [outStr appendString: currStr]; 607 | } 608 | 609 | return outStr; 610 | } 611 | 612 | 613 | 614 | -(NSUInteger) count 615 | { 616 | return [differences count]; 617 | } 618 | 619 | 620 | -(UKDiffEntry*) entryAtIndex: (NSUInteger)idx 621 | { 622 | return [differences objectAtIndex: idx]; 623 | } 624 | 625 | 626 | @end 627 | 628 | 629 | 630 | @implementation UKDiffEntry 631 | 632 | @synthesize operation; 633 | @synthesize originalRange; 634 | @synthesize destinationRange; 635 | @synthesize newText; 636 | @synthesize oldText; 637 | @synthesize apply; 638 | 639 | -(id) init 640 | { 641 | if(( self = [super init] )) 642 | { 643 | apply = YES; 644 | } 645 | return self; 646 | } 647 | 648 | -(NSString*) description 649 | { 650 | return [NSString stringWithFormat: @"%@ { operation = %c originalRange = { %ld, %ld }, destinationRange = { %ld, %ld }, apply = %s }", 651 | NSStringFromClass([self class]), operation, originalRange.location, originalRange.length, 652 | destinationRange.location, destinationRange.length, (apply? "YES":"NO")]; 653 | } 654 | 655 | @end 656 | -------------------------------------------------------------------------------- /UKDiffView.h: -------------------------------------------------------------------------------- 1 | // 2 | // UKDiffView.h 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 02.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | #import 29 | #import "UKDiffParser.h" 30 | 31 | 32 | @interface UKCachedDiffEntry : NSObject 33 | { 34 | NSRect leftDrawBox; 35 | NSRect rightDrawBox; 36 | NSTextStorage* leftTextStorage; 37 | NSTextStorage* rightTextStorage; 38 | UKDiffOperation currOp; 39 | NSDictionary* attributes; 40 | BOOL apply; 41 | } 42 | 43 | @property (assign) NSRect leftDrawBox; 44 | @property (assign) NSRect rightDrawBox; 45 | @property (retain) NSTextStorage* leftTextStorage; 46 | @property (retain) NSTextStorage* rightTextStorage; 47 | @property (assign) UKDiffOperation currOp; 48 | @property (retain) NSDictionary* attributes; 49 | @property (assign) BOOL apply; 50 | 51 | +(id) cachedEntryWithLeftString: (NSString*)leftStr rightString: (NSString*)rightStr attributes: (NSDictionary*)attrs applyFlag: (BOOL)apl; 52 | -(id) initWithLeftString: (NSString*)leftStr rightString: (NSString*)rightStr attributes: (NSDictionary*)attrs applyFlag: (BOOL)apl; 53 | 54 | -(void) drawSelected: (BOOL)selState; 55 | 56 | -(NSBezierPath*) pathWithConnectedBox: (NSRect)leftBox toBox: (NSRect)rightBox; 57 | 58 | @end 59 | 60 | 61 | 62 | @interface UKDiffView : NSView 63 | { 64 | UKDiffParser* diffParser; 65 | NSMutableArray* cachedDrawings; 66 | NSInteger selectedRow; 67 | } 68 | 69 | @property (retain) UKDiffParser* diffParser; 70 | @property (retain) NSMutableArray* cachedDrawings; 71 | @property (assign) NSInteger selectedRow; 72 | 73 | -(NSSize) bestSize; 74 | 75 | -(void) updateDrawingCacheCompletely: (BOOL)recreate; 76 | 77 | @end 78 | 79 | // Layout utility functions 80 | NSRect UKBoxAroundPoint( NSPoint pos, CGFloat dist ); 81 | NSPoint UKTopLeft( NSRect box ); 82 | NSPoint UKTopRight( NSRect box ); 83 | NSPoint UKBottomLeft( NSRect box ); 84 | NSPoint UKBottomRight( NSRect box ); 85 | NSPoint UKOffsetPoint( NSPoint pos, CGFloat x, CGFloat y ); 86 | -------------------------------------------------------------------------------- /UKDiffView.m: -------------------------------------------------------------------------------- 1 | // 2 | // UKDiffView.m 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 02.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | // ----------------------------------------------------------------------------- 29 | // Headers: 30 | // ----------------------------------------------------------------------------- 31 | 32 | #import "UKDiffView.h" 33 | #import "UKDiffParser.h" 34 | #import "UKHelperMacros.h" 35 | 36 | 37 | // ----------------------------------------------------------------------------- 38 | // Constants: 39 | // ----------------------------------------------------------------------------- 40 | 41 | #define ROUNDING_SIZE (2.0f) 42 | #define SIDE_MARGIN (3.0f) 43 | #define HORZ_MARGIN (8.0f) 44 | #define VERT_MARGIN (4.0f) 45 | #define DIVIDER_WIDTH (16.0f) 46 | 47 | 48 | // ----------------------------------------------------------------------------- 49 | // UKCachedDiffEntry: 50 | // We use this class to cache the text engine elements and positions and 51 | // sizes of our display elements for faster scrolling and hit testing. 52 | // ----------------------------------------------------------------------------- 53 | 54 | @implementation UKCachedDiffEntry 55 | 56 | @synthesize leftDrawBox; 57 | @synthesize rightDrawBox; 58 | @synthesize leftTextStorage; 59 | @synthesize rightTextStorage; 60 | @synthesize currOp; 61 | @synthesize attributes; 62 | @synthesize apply; 63 | 64 | +(id) cachedEntryWithLeftString: (NSString*)leftStr rightString: (NSString*)rightStr attributes: (NSDictionary*)attrs applyFlag: (BOOL)apl 65 | { 66 | return [[[[self class] alloc] initWithLeftString: leftStr rightString: rightStr attributes: attrs applyFlag: apl] autorelease]; 67 | } 68 | 69 | 70 | -(id) initWithLeftString: (NSString*)leftStr rightString: (NSString*)rightStr attributes: (NSDictionary*)attrs applyFlag: (BOOL)apl 71 | { 72 | if(( self = [super init] )) 73 | { 74 | NSAttributedString* leftAttrStr = [[[NSAttributedString alloc] initWithString: leftStr ? leftStr : @"" attributes: attrs] autorelease]; 75 | leftTextStorage = [[NSTextStorage alloc] initWithAttributedString: leftAttrStr]; 76 | NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init]; 77 | NSTextContainer* textContainer = [[NSTextContainer alloc] init]; 78 | [layoutManager addTextContainer:textContainer]; 79 | [textContainer release]; 80 | [leftTextStorage addLayoutManager:layoutManager]; 81 | [layoutManager release]; 82 | 83 | NSAttributedString* rightAttrStr = [[[NSAttributedString alloc] initWithString: rightStr ? rightStr : @"" attributes: attrs] autorelease]; 84 | rightTextStorage = [[NSTextStorage alloc] initWithAttributedString: rightAttrStr]; 85 | layoutManager = [[NSLayoutManager alloc] init]; 86 | textContainer = [[NSTextContainer alloc] init]; 87 | [layoutManager addTextContainer:textContainer]; 88 | [textContainer release]; 89 | [rightTextStorage addLayoutManager:layoutManager]; 90 | [layoutManager release]; 91 | 92 | apply = apl; 93 | 94 | self.attributes = attrs; 95 | } 96 | 97 | return self; 98 | } 99 | 100 | -(void) dealloc 101 | { 102 | self.leftTextStorage = nil; 103 | self.rightTextStorage = nil; 104 | self.attributes = nil; 105 | 106 | [super dealloc]; 107 | } 108 | 109 | 110 | static CGFloat sOneLineHeight = -1; 111 | 112 | 113 | -(void) setLeftDrawBox: (NSRect)box 114 | { 115 | if( sOneLineHeight < 0 ) 116 | sOneLineHeight = [@" " sizeWithAttributes: attributes].height; 117 | 118 | leftDrawBox = box; 119 | 120 | NSString* str = [leftTextStorage string]; 121 | unichar lastCh = ([str length] > 0) ? [str characterAtIndex: [str length] -1] : '\n'; 122 | BOOL endsInLineBreak = lastCh == '\n' || lastCh == '\r'; 123 | 124 | NSLayoutManager* layoutManager = [[leftTextStorage layoutManagers] objectAtIndex: 0]; 125 | NSTextContainer* textContainer = [[layoutManager textContainers] objectAtIndex: 0]; 126 | [textContainer setContainerSize: NSMakeSize(box.size.width, FLT_MAX)]; 127 | (NSRange) [layoutManager glyphRangeForTextContainer: textContainer]; // Cause re-layout. 128 | NSRect textRect = [layoutManager usedRectForTextContainer: textContainer]; 129 | leftDrawBox.size.height = textRect.size.height -(endsInLineBreak ? sOneLineHeight : 0); 130 | } 131 | 132 | 133 | -(void) setRightDrawBox: (NSRect)box 134 | { 135 | if( sOneLineHeight < 0 ) 136 | sOneLineHeight = [@" " sizeWithAttributes: attributes].height; 137 | 138 | rightDrawBox = box; 139 | 140 | NSString* str = [rightTextStorage string]; 141 | unichar lastCh = ([str length] > 0) ? [str characterAtIndex: [str length] -1] : '\n'; 142 | BOOL endsInLineBreak = lastCh == '\n' || lastCh == '\r'; 143 | 144 | NSLayoutManager* layoutManager = [[rightTextStorage layoutManagers] objectAtIndex: 0]; 145 | NSTextContainer* textContainer = [[layoutManager textContainers] objectAtIndex: 0]; 146 | [textContainer setContainerSize: NSMakeSize(box.size.width, FLT_MAX)]; 147 | (NSRange) [layoutManager glyphRangeForTextContainer: textContainer]; // Cause re-layout. 148 | NSRect textRect = [layoutManager usedRectForTextContainer: textContainer]; 149 | rightDrawBox.size.height = textRect.size.height -(endsInLineBreak ? sOneLineHeight : 0); 150 | } 151 | 152 | 153 | NSRect UKBoxAroundPoint( NSPoint pos, CGFloat dist ) 154 | { 155 | NSRect box = NSZeroRect; 156 | box.origin = pos; 157 | return NSInsetRect( box, -dist, -dist ); 158 | } 159 | 160 | 161 | NSPoint UKTopLeft( NSRect box ) 162 | { 163 | return NSMakePoint( NSMinX(box), NSMinY(box) ); 164 | } 165 | 166 | 167 | NSPoint UKTopRight( NSRect box ) 168 | { 169 | return NSMakePoint( NSMaxX(box), NSMinY(box) ); 170 | } 171 | 172 | 173 | NSPoint UKBottomLeft( NSRect box ) 174 | { 175 | return NSMakePoint( NSMinX(box), NSMaxY(box) ); 176 | } 177 | 178 | 179 | NSPoint UKBottomRight( NSRect box ) 180 | { 181 | return NSMakePoint( NSMaxX(box), NSMaxY(box) ); 182 | } 183 | 184 | 185 | NSPoint UKOffsetPoint( NSPoint pos, CGFloat x, CGFloat y ) 186 | { 187 | pos.x += x; 188 | pos.y += y; 189 | 190 | return pos; 191 | } 192 | 193 | 194 | -(NSBezierPath*) pathWithConnectedBox: (NSRect)leftBox toBox: (NSRect)rightBox 195 | { 196 | leftBox.origin.x -= SIDE_MARGIN; 197 | leftBox.size.width += SIDE_MARGIN; 198 | rightBox.size.width += SIDE_MARGIN; 199 | 200 | NSBezierPath* thePath = [NSBezierPath bezierPath]; 201 | NSRect topLeft, topRight, botRight, botLeft; 202 | CGFloat leftRounding = (leftBox.size.height >= ROUNDING_SIZE) ? ROUNDING_SIZE : 0.0f, 203 | rightRounding = (rightBox.size.height >= ROUNDING_SIZE) ? ROUNDING_SIZE : 0.0f; 204 | 205 | topLeft = UKBoxAroundPoint(UKTopLeft(leftBox), leftRounding ); 206 | topRight = UKBoxAroundPoint(UKTopRight(rightBox), rightRounding ); 207 | botRight = UKBoxAroundPoint(UKBottomRight(rightBox), rightRounding ); 208 | botLeft = UKBoxAroundPoint(UKBottomLeft(leftBox), leftRounding ); 209 | 210 | [thePath moveToPoint: UKBottomLeft(topLeft)]; 211 | [thePath appendBezierPathWithArcWithCenter: UKBottomRight(topLeft) 212 | radius: topLeft.size.height 213 | startAngle: 180.0f 214 | endAngle: 270.0f 215 | clockwise: NO]; 216 | [thePath lineToPoint: UKOffsetPoint(UKTopRight(leftBox), 0, -leftRounding)]; 217 | [thePath lineToPoint: UKOffsetPoint(UKTopLeft(rightBox), 0, -rightRounding)]; 218 | [thePath lineToPoint: UKTopLeft(topRight)]; 219 | [thePath appendBezierPathWithArcWithCenter: UKBottomLeft(topRight) 220 | radius: topRight.size.height 221 | startAngle: 270.0f 222 | endAngle: 0.0f 223 | clockwise: NO]; 224 | [thePath lineToPoint: UKOffsetPoint(UKTopRight(botRight), 0, rightRounding)]; 225 | [thePath appendBezierPathWithArcWithCenter: UKTopLeft(botRight) 226 | radius: botRight.size.height 227 | startAngle: 0.0f 228 | endAngle: 90.0f 229 | clockwise: NO]; 230 | [thePath lineToPoint: UKOffsetPoint(UKBottomLeft(rightBox), 0, rightRounding)]; 231 | [thePath lineToPoint: UKOffsetPoint(UKBottomRight(leftBox), 0, leftRounding)]; 232 | [thePath lineToPoint: UKBottomRight(botLeft)]; 233 | [thePath appendBezierPathWithArcWithCenter: UKTopRight(botLeft) 234 | radius: botLeft.size.height 235 | startAngle: 90.0f 236 | endAngle: 180.0f 237 | clockwise: NO]; 238 | [thePath closePath]; 239 | 240 | NSAffineTransform* trans = [NSAffineTransform transform]; 241 | [trans translateXBy: 0.5f yBy: 0.5f]; 242 | [thePath transformUsingAffineTransform: trans]; 243 | 244 | return thePath; 245 | } 246 | 247 | 248 | -(void) drawSelected: (BOOL)selState 249 | { 250 | // Draw a coloured box around the text to indicate the kind of operation: 251 | if( currOp != UKDiffOperationUnchanged ) 252 | { 253 | NSColor* mainColor = nil; 254 | NSColor* fillColor = nil; 255 | if( currOp == UKDiffOperationChange ) 256 | mainColor = [NSColor blueColor]; 257 | else if( currOp == UKDiffOperationDelete ) 258 | { 259 | mainColor = [NSColor redColor]; 260 | rightDrawBox.size.height = 0; 261 | } 262 | else if( currOp == UKDiffOperationAdd ) 263 | { 264 | mainColor = [NSColor greenColor]; 265 | leftDrawBox.size.height = 0; 266 | } 267 | fillColor = [mainColor colorWithAlphaComponent: 0.1f]; 268 | 269 | NSBezierPath* thePath = [self pathWithConnectedBox: leftDrawBox toBox: rightDrawBox]; 270 | 271 | [fillColor setFill]; 272 | [thePath fill]; 273 | if( selState ) 274 | { 275 | [mainColor setStroke]; 276 | [thePath setLineWidth: 2.0f]; 277 | } 278 | else 279 | { 280 | [fillColor setStroke]; 281 | [thePath setLineWidth: 1.0f]; 282 | } 283 | [thePath stroke]; 284 | 285 | [[NSColor blackColor] set]; 286 | 287 | static NSImage* sChooseLeftImg = nil; 288 | if( !sChooseLeftImg ) 289 | sChooseLeftImg = [NSImage imageNamed: @"UKDiffViewChooseLeft"]; 290 | static NSImage* sChooseRightImg = nil; 291 | if( !sChooseRightImg ) 292 | sChooseRightImg = [NSImage imageNamed: @"UKDiffViewChooseRight"]; 293 | NSImage* img = apply ? sChooseRightImg : sChooseLeftImg; 294 | [img drawAtPoint: UKTopRight(leftDrawBox) fromRect: NSZeroRect operation: NSCompositeSourceAtop fraction: 1.0f]; 295 | } 296 | 297 | NSLayoutManager* layoutManager = [[leftTextStorage layoutManagers] objectAtIndex: 0]; 298 | NSTextContainer* textContainer = [[layoutManager textContainers] objectAtIndex: 0]; 299 | NSRange glyphRange = [layoutManager glyphRangeForTextContainer: textContainer]; 300 | [layoutManager drawGlyphsForGlyphRange: glyphRange atPoint: leftDrawBox.origin]; 301 | 302 | layoutManager = [[rightTextStorage layoutManagers] objectAtIndex: 0]; 303 | textContainer = [[layoutManager textContainers] objectAtIndex: 0]; 304 | glyphRange = [layoutManager glyphRangeForTextContainer: textContainer]; 305 | [layoutManager drawGlyphsForGlyphRange: glyphRange atPoint: rightDrawBox.origin]; 306 | } 307 | 308 | @end 309 | 310 | 311 | 312 | // ----------------------------------------------------------------------------- 313 | // UKDiffView: 314 | // ----------------------------------------------------------------------------- 315 | 316 | @implementation UKDiffView 317 | 318 | @synthesize diffParser; 319 | @synthesize cachedDrawings; 320 | @synthesize selectedRow; 321 | 322 | - (id)initWithFrame:(NSRect)frame 323 | { 324 | if(( self = [super initWithFrame: frame] )) 325 | { 326 | self.cachedDrawings = [NSMutableArray array]; 327 | selectedRow = -1; 328 | } 329 | return self; 330 | } 331 | 332 | 333 | -(void) dealloc 334 | { 335 | self.diffParser = nil; 336 | self.cachedDrawings = nil; 337 | 338 | [super dealloc]; 339 | } 340 | 341 | 342 | -(NSSize) bestSize 343 | { 344 | CGFloat maxY = NSMaxY([[cachedDrawings lastObject] leftDrawBox]); 345 | CGFloat maxY2 = NSMaxY([[cachedDrawings lastObject] rightDrawBox]); 346 | NSSize bestSize = [self bounds].size; 347 | if( maxY > maxY2 ) 348 | bestSize.height = maxY + VERT_MARGIN; 349 | else 350 | bestSize.height = maxY2 +VERT_MARGIN; 351 | return bestSize; 352 | } 353 | 354 | 355 | -(void) drawRect:(NSRect)dirtyRect 356 | { 357 | NSRect visBox = [self visibleRect]; 358 | visBox = NSIntersectionRect( visBox, dirtyRect ); 359 | NSInteger idx = 0; 360 | 361 | for( UKCachedDiffEntry* cached in cachedDrawings ) 362 | { 363 | if( NSIntersectsRect( NSInsetRect([cached leftDrawBox], -3, -3), visBox ) 364 | || NSIntersectsRect( NSInsetRect([cached rightDrawBox], -3, -3), visBox ) ) 365 | [cached drawSelected: selectedRow == idx]; 366 | idx++; 367 | } 368 | } 369 | 370 | 371 | -(void) mouseDown: (NSEvent*)evt 372 | { 373 | NSPoint pos = [self convertPoint: [evt locationInWindow] fromView: nil]; 374 | NSInteger idx = 0; 375 | 376 | selectedRow = -1; 377 | 378 | for( UKCachedDiffEntry* cached in cachedDrawings ) 379 | { 380 | if( NSPointInRect( pos, [cached leftDrawBox] ) 381 | || NSPointInRect( pos, [cached rightDrawBox] ) ) 382 | { 383 | selectedRow = idx; 384 | } 385 | 386 | idx++; 387 | } 388 | 389 | [self setNeedsDisplay: YES]; 390 | } 391 | 392 | 393 | -(void) keyDown: (NSEvent*)evt 394 | { 395 | [self interpretKeyEvents: [NSArray arrayWithObject: evt]]; 396 | } 397 | 398 | 399 | -(void) moveRight: (id)sender 400 | { 401 | #pragma unused(sender) 402 | 403 | if( selectedRow >= 0 ) 404 | { 405 | UKDiffEntry* currEntry = [diffParser entryAtIndex: selectedRow]; 406 | [currEntry setApply: YES]; 407 | UKCachedDiffEntry* cached = [cachedDrawings objectAtIndex: selectedRow]; 408 | [cached setApply: YES]; 409 | [self setNeedsDisplay: YES]; 410 | } 411 | } 412 | 413 | 414 | -(void) moveLeft: (id)sender 415 | { 416 | #pragma unused(sender) 417 | 418 | if( selectedRow >= 0 ) 419 | { 420 | UKDiffEntry* currEntry = [diffParser entryAtIndex: selectedRow]; 421 | [currEntry setApply: NO]; 422 | UKCachedDiffEntry* cached = [cachedDrawings objectAtIndex: selectedRow]; 423 | [cached setApply: NO]; 424 | [self setNeedsDisplay: YES]; 425 | } 426 | } 427 | 428 | 429 | -(void) moveUp: (id)sender 430 | { 431 | #pragma unused(sender) 432 | 433 | NSInteger x = selectedRow -1; 434 | 435 | for( ; x >= 0; x-- ) 436 | { 437 | UKCachedDiffEntry* currEntry = [cachedDrawings objectAtIndex: x]; 438 | if( [currEntry currOp] != UKDiffOperationUnchanged ) 439 | { 440 | selectedRow = x; 441 | [self setNeedsDisplay: YES]; 442 | [self scrollRectToVisible: [currEntry leftDrawBox]]; 443 | [self scrollRectToVisible: [currEntry rightDrawBox]]; 444 | break; 445 | } 446 | } 447 | } 448 | 449 | 450 | -(void) moveDown: (id)sender 451 | { 452 | #pragma unused(sender) 453 | 454 | NSInteger x = selectedRow +1, 455 | count = [cachedDrawings count]; 456 | 457 | for( ; x < count; x++ ) 458 | { 459 | UKCachedDiffEntry* currEntry = [cachedDrawings objectAtIndex: x]; 460 | if( [currEntry currOp] != UKDiffOperationUnchanged ) 461 | { 462 | selectedRow = x; 463 | [self setNeedsDisplay: YES]; 464 | [self scrollRectToVisible: [currEntry leftDrawBox]]; 465 | [self scrollRectToVisible: [currEntry rightDrawBox]]; 466 | break; 467 | } 468 | } 469 | } 470 | 471 | 472 | -(BOOL) acceptsFirstResponder 473 | { 474 | return YES; 475 | } 476 | 477 | 478 | -(BOOL) becomeFirstResponder 479 | { 480 | return YES; 481 | } 482 | 483 | 484 | -(BOOL) resignFirstResponder 485 | { 486 | return YES; 487 | } 488 | 489 | 490 | -(void) setDiffParser: (UKDiffParser*)dp 491 | { 492 | ASSIGN(diffParser,dp); 493 | [self updateDrawingCacheCompletely: YES]; 494 | [self setNeedsDisplay: YES]; 495 | } 496 | 497 | 498 | -(void) setFrame: (NSRect)box 499 | { 500 | [super setFrame: box]; 501 | [self updateDrawingCacheCompletely: NO]; 502 | box.size = [self bestSize]; 503 | [super setFrame: box]; 504 | } 505 | 506 | 507 | -(void) updateDrawingCacheCompletely: (BOOL)recreate 508 | { 509 | NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys: 510 | [NSFont userFixedPitchFontOfSize: 10.0f], NSFontAttributeName, 511 | nil]; 512 | NSUInteger x = 0, 513 | count = [diffParser count]; 514 | NSRect box = NSInsetRect( [self bounds], HORZ_MARGIN, VERT_MARGIN ); 515 | CGFloat halfWidth = truncf( box.size.width / 2 ) -DIVIDER_WIDTH; 516 | NSRect leftBox = box, rightBox; 517 | leftBox.size.width = halfWidth; 518 | rightBox = leftBox; 519 | rightBox.origin.x = NSMaxX(box) -halfWidth; 520 | 521 | if( recreate ) 522 | [cachedDrawings removeAllObjects]; 523 | 524 | for( x = 0; x < count; x++ ) 525 | { 526 | UKDiffEntry* currDiff = [diffParser entryAtIndex: x]; 527 | UKCachedDiffEntry* cached = nil; 528 | 529 | if( recreate ) 530 | { 531 | cached = [UKCachedDiffEntry cachedEntryWithLeftString: [currDiff oldText] 532 | rightString: [currDiff newText] 533 | attributes: attrs 534 | applyFlag: [currDiff apply]]; 535 | [cached setCurrOp: [currDiff operation]]; 536 | [cachedDrawings addObject: cached]; 537 | } 538 | else 539 | cached = [cachedDrawings objectAtIndex: x]; 540 | 541 | [cached setLeftDrawBox: leftBox]; 542 | leftBox = [cached leftDrawBox]; 543 | [cached setRightDrawBox: rightBox]; 544 | rightBox = [cached rightDrawBox]; 545 | 546 | CGFloat maxHeight = (rightBox.size.height > leftBox.size.height) ? rightBox.size.height : leftBox.size.height; 547 | leftBox.origin.y += maxHeight; 548 | rightBox.origin.y += maxHeight; 549 | } 550 | } 551 | 552 | 553 | -(BOOL) isFlipped 554 | { 555 | return YES; 556 | } 557 | 558 | @end 559 | -------------------------------------------------------------------------------- /UKDiffViewChooseLeft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uliwitness/UKDiffView/15771d38e4ade8853606e97f88fe25579ea3dd72/UKDiffViewChooseLeft.pdf -------------------------------------------------------------------------------- /UKDiffViewChooseRight.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uliwitness/UKDiffView/15771d38e4ade8853606e97f88fe25579ea3dd72/UKDiffViewChooseRight.pdf -------------------------------------------------------------------------------- /UKObjectLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // UKObjectLogger.h 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 05.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | #import 29 | 30 | 31 | @interface UKObjectLogger : NSObject 32 | { 33 | 34 | } 35 | 36 | 37 | +(void) installUKObjectLogger; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /UKObjectLogger.m: -------------------------------------------------------------------------------- 1 | // 2 | // UKObjectLogger.m 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 05.08.08. 6 | // Copyright 2008 The Void Software. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | #import "UKObjectLogger.h" 29 | 30 | 31 | @implementation UKObjectLogger 32 | 33 | +(void) installUKObjectLogger 34 | { 35 | [[self class] poseAsClass: [NSObject class]]; 36 | } 37 | 38 | -(id) init 39 | { 40 | if(( self = [super init] )) 41 | { 42 | NSLog(@"Instantiated %@", NSStringFromClass([self class])); 43 | } 44 | return self; 45 | } 46 | 47 | -(void) dealloc 48 | { 49 | NSLog(@"Dealloced %@", NSStringFromClass([self class])); 50 | [super dealloc]; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AngelDiff 4 | // 5 | // Created by Uli Kusterer on 02.08.08. 6 | // Copyright The Void Software 2008. All rights reserved. 7 | // 8 | // This software is provided 'as-is', without any express or implied 9 | // warranty. In no event will the authors be held liable for any damages 10 | // arising from the use of this software. 11 | // 12 | // Permission is granted to anyone to use this software for any purpose, 13 | // including commercial applications, and to alter it and redistribute it 14 | // freely, subject to the following restrictions: 15 | // 16 | // 1. The origin of this software must not be misrepresented; you must not 17 | // claim that you wrote the original software. If you use this software 18 | // in a product, an acknowledgment in the product documentation would be 19 | // appreciated but is not required. 20 | // 21 | // 2. Altered source versions must be plainly marked as such, and must not be 22 | // misrepresented as being the original software. 23 | // 24 | // 3. This notice may not be removed or altered from any source 25 | // distribution. 26 | // 27 | 28 | #import 29 | #import "UKObjectLogger.h" 30 | 31 | 32 | int main(int argc, char *argv[]) 33 | { 34 | //[UKObjectLogger installUKObjectLogger]; 35 | 36 | return NSApplicationMain(argc, (const char **) argv); 37 | } 38 | --------------------------------------------------------------------------------