/core/externals/google-toolbox-for-mac/XcodePlugin/GTMXcodeCorrectWhiteSpace.m

http://macfuse.googlecode.com/ · Objective C · 208 lines · 151 code · 18 blank · 39 comment · 44 complexity · 0a706a74b3353a25e02fb7df27299e62 MD5 · raw file

  1. //
  2. // GTMXcodeCorrectWhiteSpace.m
  3. //
  4. // Copyright 2009 Google Inc.
  5. //
  6. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7. // use this file except in compliance with the License. You may obtain a copy
  8. // of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. // License for the specific language governing permissions and limitations under
  16. // the License.
  17. //
  18. #import <Cocoa/Cocoa.h>
  19. #import "GTMObjC2Runtime.h"
  20. #import "GTMXcodePlugin.h"
  21. #import "GTMXcodePreferences.h"
  22. @interface GTMXcodeCorrectWhiteSpace : NSObject
  23. - (BOOL)gdt_writeToFile:(NSString *)fileName ofType:(NSString *)type;
  24. @end
  25. @implementation GTMXcodeCorrectWhiteSpace
  26. // Register our class to perform the swizzle once the plugin has finished
  27. // loading.
  28. + (void)load {
  29. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  30. [GTMXcodePlugin registerSwizzleClass:self];
  31. [pool release];
  32. }
  33. // Default initializer. Swizzles [PBXTextFileDocument writeToFile:ofType:]
  34. // with our [gdt_writeToFile:ofType:].
  35. - (id)init {
  36. if ((self = [super init])) {
  37. SEL ourSelector = @selector(gdt_writeToFile:ofType:);
  38. Method ourMethod = class_getInstanceMethod([self class], ourSelector);
  39. Class pbxClass = NSClassFromString(@"PBXTextFileDocument");
  40. if (class_addMethod(pbxClass,
  41. ourSelector,
  42. method_getImplementation(ourMethod),
  43. method_getTypeEncoding(ourMethod))) {
  44. ourMethod = class_getInstanceMethod(pbxClass, ourSelector);
  45. Method theirMethod
  46. = class_getInstanceMethod(pbxClass, @selector(writeToFile:ofType:));
  47. method_exchangeImplementations(ourMethod, theirMethod);
  48. }
  49. }
  50. return self;
  51. }
  52. // Perform a "subtraction of ranges". A - B. Not transitive.
  53. static NSRange SubtractRange(NSRange a, NSRange b) {
  54. NSRange newRange;
  55. NSUInteger maxRangeA = NSMaxRange(a);
  56. NSUInteger maxRangeB = NSMaxRange(b);
  57. if (b.location == NSNotFound) {
  58. // B is bogus
  59. newRange = a;
  60. } else if (maxRangeB <= a.location) {
  61. // B is completely before A
  62. newRange = NSMakeRange(a.location - b.length, a.length);
  63. } else if (maxRangeA <= b.location) {
  64. // B is completely after A
  65. newRange = a;
  66. } else if (b.location <= a.location && maxRangeB >= maxRangeA) {
  67. // B subsumes A
  68. newRange = NSMakeRange(b.location, 0);
  69. } else if (a.location <= b.location && maxRangeA >= maxRangeB) {
  70. // A subsumes B
  71. newRange = NSMakeRange(a.location, a.length - b.length);
  72. } else if (b.location <= a.location && maxRangeB <= maxRangeA) {
  73. // B overlaps front edge of A
  74. NSUInteger diff = maxRangeB - a.location;
  75. newRange = NSMakeRange(a.location + diff, a.length - diff);
  76. } else if (b.location <= maxRangeA && maxRangeB >= maxRangeA) {
  77. // B overlaps back edge of A
  78. NSUInteger diff = maxRangeA - b.location;
  79. newRange = NSMakeRange(a.location, a.length - diff);
  80. }
  81. return newRange;
  82. }
  83. + (BOOL)gdt_writeToFile:(NSString *)fileName
  84. ofType:(NSString *)type
  85. object:(id)object {
  86. NSTextStorage *storage = [(id)object textStorage];
  87. id delegate = [storage delegate];
  88. // Need to keep track of all the current selections so that we can replace
  89. // them after stripping off the whitespace. A single source file can have
  90. // multiple views, so we store one selection per view.
  91. NSArray *windowControllers = [delegate windowControllers];
  92. size_t size = sizeof(NSRange) * [windowControllers count];
  93. NSRange *ranges = [[NSMutableData dataWithLength:size] mutableBytes];
  94. NSUInteger rangeCount = 0;
  95. for (id controller in windowControllers) {
  96. if ([controller respondsToSelector:@selector(textView)]) {
  97. NSTextView *textView = [controller textView];
  98. ranges[rangeCount] = [textView selectedRange];
  99. rangeCount++;
  100. }
  101. }
  102. NSMutableString *text = [[[storage string] mutableCopy] autorelease];
  103. NSRange oldRange = NSMakeRange(0, [text length]);
  104. // Figure out the newlines in our file.
  105. NSString *newlineString = @"\n";
  106. if ([text rangeOfString:@"\r\n"].length > 0) {
  107. newlineString = @"\r\n";
  108. } else if ([text rangeOfString:@"\r"].length > 0) {
  109. newlineString = @"\r";
  110. }
  111. NSUInteger newlineStringLength = [newlineString length];
  112. NSCharacterSet *whiteSpace
  113. = [NSCharacterSet characterSetWithCharactersInString:@" \t"];
  114. NSMutableCharacterSet *nonWhiteSpace = [[whiteSpace mutableCopy] autorelease];
  115. [nonWhiteSpace invert];
  116. // If the file is missing a newline at the end, add it now.
  117. if (![text hasSuffix:newlineString]) {
  118. [text appendString:newlineString];
  119. }
  120. NSRange textRange = NSMakeRange(0, [text length] - 1);
  121. while (textRange.length > 0) {
  122. NSRange lineRange = [text rangeOfString:newlineString
  123. options:NSBackwardsSearch
  124. range:textRange];
  125. if (lineRange.location == NSNotFound) {
  126. lineRange.location = 0;
  127. } else {
  128. lineRange.location += newlineStringLength;
  129. }
  130. lineRange.length = textRange.length - lineRange.location;
  131. textRange.length = lineRange.location;
  132. if (textRange.length != 0) {
  133. textRange.length -= newlineStringLength;
  134. }
  135. NSRange whiteRange = [text rangeOfCharacterFromSet:whiteSpace
  136. options:NSBackwardsSearch
  137. range:lineRange];
  138. if (NSMaxRange(whiteRange) == NSMaxRange(lineRange)) {
  139. NSRange nonWhiteRange = [text rangeOfCharacterFromSet:nonWhiteSpace
  140. options:NSBackwardsSearch
  141. range:lineRange];
  142. NSRange deleteRange;
  143. if (nonWhiteRange.location == NSNotFound) {
  144. deleteRange.location = lineRange.location;
  145. } else {
  146. deleteRange.location = NSMaxRange(nonWhiteRange);
  147. }
  148. deleteRange.length = NSMaxRange(whiteRange) - deleteRange.location;
  149. [text deleteCharactersInRange:deleteRange];
  150. // Update all the selections appropriately.
  151. for (NSUInteger i = 0; i < rangeCount; ++i) {
  152. NSRange baseRange = ranges[i];
  153. NSRange newRange = SubtractRange(baseRange, deleteRange);
  154. ranges[i] = newRange;
  155. }
  156. }
  157. }
  158. // Replace the text with the new stripped version.
  159. [storage beginEditing];
  160. [storage replaceCharactersInRange:oldRange withString:text];
  161. [storage endEditing];
  162. // Fix up selections
  163. NSUInteger count = 0;
  164. for (id controller in windowControllers) {
  165. if ([controller respondsToSelector:@selector(textView)]) {
  166. NSRange newRange = ranges[count];
  167. if (newRange.location != NSNotFound) {
  168. NSTextView *textView = [controller textView];
  169. [textView setSelectedRange:ranges[count]];
  170. }
  171. count++;
  172. }
  173. }
  174. // Finish the save.
  175. return [object gdt_writeToFile:fileName ofType:type];
  176. }
  177. - (BOOL)gdt_writeToFile:(NSString *)fileName ofType:(NSString *)type {
  178. BOOL isGood;
  179. // Check our defaults to see if we want to strip whitespace.
  180. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  181. if ([defaults boolForKey:GTMXcodeCorrectWhiteSpaceOnSave]) {
  182. isGood = [GTMXcodeCorrectWhiteSpace gdt_writeToFile:fileName
  183. ofType:type
  184. object:self];
  185. } else {
  186. isGood = [self gdt_writeToFile:fileName ofType:type];
  187. }
  188. return isGood;
  189. }
  190. @end