├── .gitignore ├── Code ├── BBUFunctionStatistics.h ├── BBUFunctionStatistics.m ├── BBUPuncoverPlugin.h └── BBUPuncoverPlugin.m ├── LICENSE ├── PuncoverPlugin.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── PuncoverPlugin.xcscheme ├── README.md ├── Resources └── test.json ├── Supporting Files ├── PuncoverPlugin-Info.plist └── PuncoverPlugin-Prefix.pch └── screenshots └── puncover-plugin.png /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | Makefile.local 3 | *.swp 4 | *tags 5 | 6 | # OS X 7 | #################### 8 | .Spotlight-V100/ 9 | .Trashes/ 10 | ._* 11 | .AppleDouble 12 | .DS_Store 13 | .LSOverride 14 | Icon? 15 | 16 | # Xcode 17 | #################### 18 | *.mode1v3 19 | *.mode2v3 20 | *.pbxuser 21 | *.perspectivev3 22 | *.user 23 | *.xcuserstate 24 | *.moved-aside 25 | *~.nib 26 | .idea/ 27 | DerivedData/ 28 | project.xcworkspace/ 29 | xcuserdata/ 30 | profile 31 | !default.pbxuser 32 | !default.mode1v3 33 | !default.mode2v3 34 | !default.perspectivev3 35 | 36 | # CocoaPods 37 | #################### 38 | Pods 39 | *.xcworkspace 40 | 41 | -------------------------------------------------------------------------------- /Code/BBUFunctionStatistics.h: -------------------------------------------------------------------------------- 1 | // 2 | // BBUFunctionStatistics.h 3 | // PuncoverPlugin 4 | // 5 | // Created by Boris Bügling on 06/09/14. 6 | // Copyright (c) 2014 Boris Bügling. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BBUFunctionStatistics : NSObject 12 | 13 | @property (nonatomic, readonly) NSColor* backgroundColor; 14 | @property (nonatomic, readonly) NSUInteger lineNumber; 15 | @property (nonatomic, readonly) NSString* longText; 16 | @property (nonatomic, readonly) NSString* shortText; 17 | @property (nonatomic, readonly) NSURL* url; 18 | 19 | +(NSArray*)functionStatisticsForFileAtPath:(NSString*)path 20 | forWorkspaceAtPath:(NSString*)workspacePath; 21 | +(NSString*)widestShortTextForFileAtPath:(NSString*)path 22 | forWorkspaceAtPath:(NSString*)workspacePath; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Code/BBUFunctionStatistics.m: -------------------------------------------------------------------------------- 1 | // 2 | // BBUFunctionStatistics.m 3 | // PuncoverPlugin 4 | // 5 | // Created by Boris Bügling on 06/09/14. 6 | // Copyright (c) 2014 Boris Bügling. All rights reserved. 7 | // 8 | 9 | #import "BBUFunctionStatistics.h" 10 | 11 | static NSString* const kLastModified = @"LastModifiedKey"; 12 | static NSString* const kJSONData = @"JSONDataKey"; 13 | 14 | static NSMutableDictionary* JSONLastModified; 15 | 16 | @interface BBUFunctionStatistics () 17 | 18 | @property (nonatomic) NSColor* backgroundColor; 19 | @property (nonatomic) NSUInteger lineNumber; 20 | @property (nonatomic) NSString* longText; 21 | @property (nonatomic) NSString* shortText; 22 | @property (nonatomic) NSURL* url; 23 | 24 | @end 25 | 26 | #pragma mark - 27 | 28 | @implementation BBUFunctionStatistics 29 | 30 | +(void)load { 31 | JSONLastModified = [@{} mutableCopy]; 32 | } 33 | 34 | +(NSColor*)colorWithHexColorString:(NSString*)inColorString { 35 | if (!inColorString) { 36 | return nil; 37 | } 38 | 39 | unsigned colorCode = 0; 40 | 41 | NSScanner* scanner = [NSScanner scannerWithString:inColorString]; 42 | [scanner scanHexInt:&colorCode]; 43 | 44 | unsigned char redByte = (unsigned char)(colorCode >> 16); 45 | unsigned char greenByte = (unsigned char)(colorCode >> 8); 46 | unsigned char blueByte = (unsigned char)(colorCode); 47 | 48 | return [NSColor 49 | colorWithCalibratedRed:(CGFloat)redByte / 0xff 50 | green:(CGFloat)greenByte / 0xff 51 | blue:(CGFloat)blueByte / 0xff 52 | alpha:1.0]; 53 | } 54 | 55 | +(NSDictionary*)fileStatisticsForWorkspaceAtPath:(NSString*)workspacePath { 56 | NSString* filePath = [workspacePath stringByAppendingPathComponent:@".gutter.json"]; 57 | NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; 58 | NSDate* currentModified = attrs[NSFileModificationDate]; 59 | 60 | if (!attrs) { 61 | return nil; 62 | } 63 | 64 | NSDictionary* cachedData = JSONLastModified[filePath]; 65 | 66 | if (cachedData) { 67 | NSDate* lastModified = cachedData[kLastModified]; 68 | if ([lastModified compare:currentModified] != NSOrderedAscending) { 69 | return cachedData[kJSONData]; 70 | } 71 | } 72 | 73 | NSData* JSONData = [NSData dataWithContentsOfFile:filePath]; 74 | 75 | if (!JSONData) { 76 | return nil; 77 | } 78 | 79 | NSDictionary* JSONDict = [NSJSONSerialization JSONObjectWithData:JSONData options:0 error:nil]; 80 | JSONLastModified[filePath] = @{ kLastModified: currentModified, kJSONData: JSONDict }; 81 | return JSONDict; 82 | } 83 | 84 | +(NSArray*)functionStatisticsForFileAtPath:(NSString*)path 85 | forWorkspaceAtPath:(NSString*)workspacePath { 86 | if (!path || !workspacePath) { 87 | return nil; 88 | } 89 | 90 | path = [path stringByReplacingOccurrencesOfString:workspacePath withString:@""]; 91 | 92 | if (path.length < 1) { 93 | return @[]; 94 | } 95 | 96 | path = [path substringFromIndex:1]; 97 | 98 | NSDictionary* fileStatistics = [self fileStatisticsForWorkspaceAtPath:workspacePath]; 99 | NSArray* fileStatistic = fileStatistics[@"symbols_by_file"][path]; 100 | NSMutableArray* result = [@[] mutableCopy]; 101 | 102 | for (NSDictionary* functionStatistic in fileStatistic) { 103 | [result addObject:[[[self class] alloc] initWithDictionary:functionStatistic]]; 104 | } 105 | 106 | return result; 107 | } 108 | 109 | +(NSString*)widestShortTextForFileAtPath:(NSString*)path 110 | forWorkspaceAtPath:(NSString*)workspacePath { 111 | NSArray* functionStatistics = [self functionStatisticsForFileAtPath:path 112 | forWorkspaceAtPath:workspacePath]; 113 | NSString* widestShortText = @""; 114 | 115 | for (BBUFunctionStatistics* functionStatistic in functionStatistics) { 116 | if (functionStatistic.shortText.length > widestShortText.length) { 117 | widestShortText = functionStatistic.shortText; 118 | } 119 | } 120 | 121 | return widestShortText; 122 | } 123 | 124 | #pragma mark - 125 | 126 | -(instancetype)initWithDictionary:(NSDictionary*)dictionary { 127 | self = [super init]; 128 | if (self) { 129 | self.backgroundColor = [[self class] colorWithHexColorString:dictionary[@"background_color"]]; 130 | self.lineNumber = [dictionary[@"line"] unsignedIntegerValue]; 131 | self.longText = dictionary[@"long_text"]; 132 | self.shortText = dictionary[@"short_text"]; 133 | 134 | NSString* urlString = dictionary[@"url"]; 135 | if (urlString) { 136 | self.url = [NSURL URLWithString:urlString]; 137 | } 138 | } 139 | return self; 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /Code/BBUPuncoverPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // BBUPuncoverPlugin.h 3 | // BBUPuncoverPlugin 4 | // 5 | // Created by Boris Bügling on 06/09/14. 6 | // Copyright (c) 2014 Boris Bügling. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BBUPuncoverPlugin : NSObject 12 | 13 | @end -------------------------------------------------------------------------------- /Code/BBUPuncoverPlugin.m: -------------------------------------------------------------------------------- 1 | // 2 | // BBUPuncoverPlugin.m 3 | // BBUPuncoverPlugin 4 | // 5 | // Created by Boris Bügling on 06/09/14. 6 | // Copyright (c) 2014 Boris Bügling. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "BBUFunctionStatistics.h" 12 | #import "BBUPuncoverPlugin.h" 13 | 14 | static BBUPuncoverPlugin *sharedPlugin; 15 | 16 | @interface BBUPuncoverPlugin() 17 | 18 | @property (nonatomic, strong) NSBundle *bundle; 19 | @property (nonatomic, copy) NSString* currentDocumentPath; 20 | @property (nonatomic, strong) NSTextView* popover; 21 | @property (nonatomic, strong) NSDictionary* statsForCurrentDocument; 22 | @property (nonatomic, strong) NSAttributedString* widestAttributedStringForCurrentDocument; 23 | 24 | @end 25 | 26 | #pragma mark - 27 | 28 | @implementation BBUPuncoverPlugin 29 | 30 | + (void)pluginDidLoad:(NSBundle *)plugin 31 | { 32 | static dispatch_once_t onceToken; 33 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 34 | if ([currentApplicationName isEqual:@"Xcode"]) { 35 | dispatch_once(&onceToken, ^{ 36 | sharedPlugin = [[self alloc] initWithBundle:plugin]; 37 | 38 | sharedPlugin.popover = [[NSTextView alloc] initWithFrame:NSZeroRect]; 39 | sharedPlugin.popover.backgroundColor = [NSColor colorWithRed:254.0/255.0 40 | green:253.0/255.0 41 | blue:204.0/255.0 42 | alpha:1.0]; 43 | sharedPlugin.popover.wantsLayer = YES; 44 | sharedPlugin.popover.layer.cornerRadius = 6.0; 45 | 46 | __block Class aClass = NSClassFromString(@"DVTTextSidebarView"); 47 | [self swizzleClass:aClass 48 | exchange:@selector(annotationAtSidebarPoint:) 49 | with:@selector(puncover_annotationAtSidebarPoint:)]; 50 | [self swizzleClass:aClass 51 | exchange:@selector(_drawLineNumbersInSidebarRect:foldedIndexes:count:linesToInvert:linesToReplace:getParaRectBlock:) 52 | with:@selector(puncover_drawLineNumbersInSidebarRect:foldedIndexes:count:linesToInvert:linesToReplace:getParaRectBlock:)]; 53 | [self swizzleClass:aClass 54 | exchange:@selector(sidebarWidth) 55 | with:@selector(puncover_sidebarWidth)]; 56 | }); 57 | } 58 | } 59 | 60 | + (void)swizzleClass:(Class)aClass exchange:(SEL)origMethod with:(SEL)altMethod 61 | { 62 | method_exchangeImplementations(class_getInstanceMethod(aClass, origMethod), 63 | class_getInstanceMethod(aClass, altMethod)); 64 | } 65 | 66 | #pragma mark - 67 | 68 | - (void)dealloc 69 | { 70 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 71 | } 72 | 73 | - (id)initWithBundle:(NSBundle *)plugin 74 | { 75 | if (self = [super init]) { 76 | self.bundle = plugin; 77 | } 78 | return self; 79 | } 80 | 81 | @end 82 | 83 | #pragma mark - 84 | 85 | @interface NSRulerView (Puncover) 86 | 87 | @property(nonatomic, readonly) NSURL* currentDocumentURL; 88 | @property(nonatomic) BOOL drawsLineNumbers; 89 | @property(retain, nonatomic) NSFont *lineNumberFont; 90 | @property(copy, nonatomic) NSColor *lineNumberTextColor; 91 | @property(assign, nonatomic) double sidebarWidth; 92 | 93 | - (void)getParagraphRect:(CGRect *)a0 firstLineRect:(CGRect *)a1 forLineNumber:(NSUInteger)a2; 94 | - (NSUInteger)lineNumberForPoint:(CGPoint)a0; 95 | 96 | @end 97 | 98 | #pragma mark - 99 | 100 | @implementation NSRulerView (Puncover) 101 | 102 | @dynamic drawsLineNumbers; 103 | @dynamic lineNumberFont; 104 | @dynamic lineNumberTextColor; 105 | @dynamic sidebarWidth; 106 | 107 | #pragma mark - 108 | 109 | - (NSDictionary*)functionStatistics { 110 | NSString* path = self.currentDocumentURL.path; 111 | 112 | if (![path isEqualToString:sharedPlugin.currentDocumentPath]) { 113 | NSArray* stats = [BBUFunctionStatistics functionStatisticsForFileAtPath:path 114 | forWorkspaceAtPath:[self workspacePath]]; 115 | 116 | NSMutableDictionary* mutableStatsDictionary = [@{} mutableCopy]; 117 | for (BBUFunctionStatistics* stat in stats) { 118 | mutableStatsDictionary[@(stat.lineNumber)] = stat; 119 | } 120 | 121 | sharedPlugin.currentDocumentPath = path; 122 | sharedPlugin.statsForCurrentDocument = [mutableStatsDictionary copy]; 123 | sharedPlugin.widestAttributedStringForCurrentDocument = [self puncover_attributedStringFromString:[BBUFunctionStatistics widestShortTextForFileAtPath:self.currentDocumentURL.path forWorkspaceAtPath:[self workspacePath]]]; 124 | } 125 | 126 | return sharedPlugin.statsForCurrentDocument; 127 | } 128 | 129 | - (NSTextView *)sourceTextView 130 | { 131 | return [[self superview] respondsToSelector:@selector(delegate)] ? (NSTextView *)[(id)[self superview] delegate] : nil; 132 | } 133 | 134 | - (id)puncover_annotationAtSidebarPoint:(CGPoint)p0 135 | { 136 | id annotation = [self puncover_annotationAtSidebarPoint:p0]; 137 | NSTextView *popover = sharedPlugin.popover; 138 | 139 | if ( !annotation && p0.x < self.sidebarWidth ) { 140 | NSUInteger line = [self lineNumberForPoint:p0]; 141 | NSDictionary* statsDictionary = [self functionStatistics]; 142 | BBUFunctionStatistics* stat = statsDictionary[@(line)]; 143 | 144 | if (stat.longText.length < 1) { 145 | return annotation; 146 | } 147 | 148 | NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:stat.longText attributes:nil]; 149 | 150 | [[popover textStorage] setAttributedString:attributedString]; 151 | 152 | CGRect a0, a1; 153 | [self getParagraphRect:&a0 firstLineRect:&a1 forLineNumber:stat.lineNumber]; 154 | 155 | NSTextView *sourceTextView = [self sourceTextView]; 156 | NSFont *font = popover.font = sourceTextView.font; 157 | 158 | CGFloat lineHeight = font.ascender + font.descender + font.leading; 159 | CGFloat w = NSWidth(sourceTextView.frame); 160 | CGFloat h = lineHeight * [popover.string componentsSeparatedByString:@"\n"].count; 161 | 162 | popover.frame = NSMakeRect(NSWidth(self.frame)+1., a0.origin.y, w, h); 163 | 164 | [self.scrollView addSubview:popover]; 165 | return annotation; 166 | } 167 | 168 | if ( [popover superview] ) { 169 | [popover removeFromSuperview]; 170 | } 171 | 172 | return annotation; 173 | } 174 | 175 | - (NSAttributedString*)puncover_attributedStringFromString:(NSString*)string { 176 | if (!self.lineNumberFont || !self.lineNumberTextColor) { 177 | return nil; 178 | } 179 | 180 | NSDictionary* attributes = @{ NSFontAttributeName: self.lineNumberFont, 181 | NSForegroundColorAttributeName: self.lineNumberTextColor }; 182 | return [[NSAttributedString alloc] initWithString:string attributes:attributes]; 183 | } 184 | 185 | - (void)puncover_drawBackground:(NSColor*)color atLine:(NSUInteger)lineNumber { 186 | if (!color) { 187 | return; 188 | } 189 | 190 | NSRect a0, a1; 191 | [self getParagraphRect:&a0 firstLineRect:&a1 forLineNumber:lineNumber]; 192 | 193 | [color set]; 194 | NSRectFill(a0); 195 | } 196 | 197 | - (void)puncover_drawString:(NSString*)string atLine:(NSUInteger)lineNumber { 198 | NSRect a0, a1; 199 | [self getParagraphRect:&a0 firstLineRect:&a1 forLineNumber:lineNumber]; 200 | 201 | NSAttributedString* currentText = [self puncover_attributedStringFromString:string]; 202 | 203 | a0.origin.x += 8.0; 204 | a0.origin.y -= 1.0; 205 | [currentText drawAtPoint:a0.origin]; 206 | } 207 | 208 | - (void)puncover_drawLineNumbersInSidebarRect:(CGRect)rect 209 | foldedIndexes:(NSUInteger *)indexes 210 | count:(NSUInteger)indexCount 211 | linesToInvert:(id)a3 212 | linesToReplace:(id)a4 213 | getParaRectBlock:rectBlock 214 | { 215 | NSDictionary* statsDictionary = [self functionStatistics]; 216 | 217 | [self lockFocus]; 218 | 219 | for (int i = 0; i < indexCount; i++) { 220 | NSUInteger lineNumber = indexes[i]; 221 | NSColor* backgroundColor = [statsDictionary[@(lineNumber)] backgroundColor]; 222 | NSString* shortText = [statsDictionary[@(lineNumber)] shortText]; 223 | 224 | for (NSString* line in [shortText componentsSeparatedByString:@"\n"]) { 225 | [self puncover_drawBackground:backgroundColor atLine:lineNumber]; 226 | [self puncover_drawString:line atLine:lineNumber++]; 227 | } 228 | } 229 | 230 | [self unlockFocus]; 231 | 232 | [self puncover_drawLineNumbersInSidebarRect:rect 233 | foldedIndexes:indexes 234 | count:indexCount 235 | linesToInvert:a3 236 | linesToReplace:a4 237 | getParaRectBlock:rectBlock]; 238 | } 239 | 240 | - (double)puncover_sidebarWidth { 241 | double originalWidth = [self puncover_sidebarWidth]; 242 | 243 | if (!self.drawsLineNumbers) { 244 | return originalWidth; 245 | } 246 | 247 | [self functionStatistics]; 248 | 249 | NSAttributedString* widestAttributedString = sharedPlugin.widestAttributedStringForCurrentDocument; 250 | 251 | return MIN(100.0, [widestAttributedString size].width + originalWidth); 252 | } 253 | 254 | - (NSURL*)currentDocumentURL { 255 | id workspaceWindowController = [self keyWorkspaceWindowController]; 256 | NSAssert(workspaceWindowController, @"No open window found."); 257 | 258 | id editorArea = [workspaceWindowController performSelector:@selector(editorArea)]; 259 | id document = [editorArea performSelector:@selector(primaryEditorDocument)]; 260 | 261 | return [document fileURL]; 262 | } 263 | 264 | -(NSString*)workspacePath { 265 | id workspaceWindowController = [self keyWorkspaceWindowController]; 266 | id workspace = [workspaceWindowController valueForKey:@"_workspace"]; 267 | 268 | NSString* path = [[workspace valueForKey:@"representingFilePath"] valueForKey:@"_pathString"]; 269 | return [path stringByDeletingLastPathComponent]; 270 | } 271 | 272 | - (id)keyWorkspaceWindowController { 273 | NSArray* workspaceWindowControllers = [objc_getClass("IDEWorkspaceWindowController") 274 | performSelector:@selector(workspaceWindowControllers)]; 275 | 276 | for (NSWindowController* controller in workspaceWindowControllers) { 277 | if ([controller.window isKeyWindow]) { 278 | return controller; 279 | } 280 | } 281 | 282 | return workspaceWindowControllers[0]; 283 | } 284 | 285 | @end 286 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Boris Buegling (http://buegling.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /PuncoverPlugin.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A1C8AF0519BB7A770079B96A /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C8AF0419BB7A770079B96A /* AppKit.framework */; }; 11 | A1C8AF0719BB7A770079B96A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C8AF0619BB7A770079B96A /* Foundation.framework */; }; 12 | A1C8AF1019BB7A770079B96A /* BBUPuncoverPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = A1C8AF0F19BB7A770079B96A /* BBUPuncoverPlugin.m */; }; 13 | A1EA312019BBAB5D0088DE5B /* test.json in Resources */ = {isa = PBXBuildFile; fileRef = A1EA311F19BBAB5D0088DE5B /* test.json */; }; 14 | A1EA312319BBABBF0088DE5B /* BBUFunctionStatistics.m in Sources */ = {isa = PBXBuildFile; fileRef = A1EA312219BBABBF0088DE5B /* BBUFunctionStatistics.m */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | A1C8AF0119BB7A770079B96A /* PuncoverPlugin.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PuncoverPlugin.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | A1C8AF0419BB7A770079B96A /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 20 | A1C8AF0619BB7A770079B96A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 21 | A1C8AF0A19BB7A770079B96A /* PuncoverPlugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PuncoverPlugin-Info.plist"; sourceTree = ""; }; 22 | A1C8AF0E19BB7A770079B96A /* BBUPuncoverPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BBUPuncoverPlugin.h; sourceTree = ""; }; 23 | A1C8AF0F19BB7A770079B96A /* BBUPuncoverPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BBUPuncoverPlugin.m; sourceTree = ""; }; 24 | A1C8AF1119BB7A770079B96A /* PuncoverPlugin-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PuncoverPlugin-Prefix.pch"; sourceTree = ""; }; 25 | A1EA311F19BBAB5D0088DE5B /* test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = test.json; sourceTree = ""; }; 26 | A1EA312119BBABBF0088DE5B /* BBUFunctionStatistics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BBUFunctionStatistics.h; sourceTree = ""; }; 27 | A1EA312219BBABBF0088DE5B /* BBUFunctionStatistics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BBUFunctionStatistics.m; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | A1C8AEFE19BB7A760079B96A /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | A1C8AF0519BB7A770079B96A /* AppKit.framework in Frameworks */, 36 | A1C8AF0719BB7A770079B96A /* Foundation.framework in Frameworks */, 37 | ); 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXFrameworksBuildPhase section */ 41 | 42 | /* Begin PBXGroup section */ 43 | A1C8AEF819BB7A760079B96A = { 44 | isa = PBXGroup; 45 | children = ( 46 | A1C8AF0819BB7A770079B96A /* Code */, 47 | A1C8AF0319BB7A770079B96A /* Frameworks */, 48 | A1C8AF0219BB7A770079B96A /* Products */, 49 | A1EA311E19BBAB500088DE5B /* Resources */, 50 | A1C8AF0919BB7A770079B96A /* Supporting Files */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | A1C8AF0219BB7A770079B96A /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | A1C8AF0119BB7A770079B96A /* PuncoverPlugin.xcplugin */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | A1C8AF0319BB7A770079B96A /* Frameworks */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | A1C8AF0419BB7A770079B96A /* AppKit.framework */, 66 | A1C8AF0619BB7A770079B96A /* Foundation.framework */, 67 | ); 68 | name = Frameworks; 69 | sourceTree = ""; 70 | }; 71 | A1C8AF0819BB7A770079B96A /* Code */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | A1EA312119BBABBF0088DE5B /* BBUFunctionStatistics.h */, 75 | A1EA312219BBABBF0088DE5B /* BBUFunctionStatistics.m */, 76 | A1C8AF0E19BB7A770079B96A /* BBUPuncoverPlugin.h */, 77 | A1C8AF0F19BB7A770079B96A /* BBUPuncoverPlugin.m */, 78 | ); 79 | path = Code; 80 | sourceTree = ""; 81 | }; 82 | A1C8AF0919BB7A770079B96A /* Supporting Files */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | A1C8AF0A19BB7A770079B96A /* PuncoverPlugin-Info.plist */, 86 | A1C8AF1119BB7A770079B96A /* PuncoverPlugin-Prefix.pch */, 87 | ); 88 | path = "Supporting Files"; 89 | sourceTree = ""; 90 | }; 91 | A1EA311E19BBAB500088DE5B /* Resources */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | A1EA311F19BBAB5D0088DE5B /* test.json */, 95 | ); 96 | path = Resources; 97 | sourceTree = ""; 98 | }; 99 | /* End PBXGroup section */ 100 | 101 | /* Begin PBXNativeTarget section */ 102 | A1C8AF0019BB7A760079B96A /* PuncoverPlugin */ = { 103 | isa = PBXNativeTarget; 104 | buildConfigurationList = A1C8AF1419BB7A770079B96A /* Build configuration list for PBXNativeTarget "PuncoverPlugin" */; 105 | buildPhases = ( 106 | A1C8AEFD19BB7A760079B96A /* Sources */, 107 | A1C8AEFE19BB7A760079B96A /* Frameworks */, 108 | A1C8AEFF19BB7A760079B96A /* Resources */, 109 | ); 110 | buildRules = ( 111 | ); 112 | dependencies = ( 113 | ); 114 | name = PuncoverPlugin; 115 | productName = PuncoverPlugin; 116 | productReference = A1C8AF0119BB7A770079B96A /* PuncoverPlugin.xcplugin */; 117 | productType = "com.apple.product-type.bundle"; 118 | }; 119 | /* End PBXNativeTarget section */ 120 | 121 | /* Begin PBXProject section */ 122 | A1C8AEF919BB7A760079B96A /* Project object */ = { 123 | isa = PBXProject; 124 | attributes = { 125 | CLASSPREFIX = BBU; 126 | LastUpgradeCheck = 0510; 127 | ORGANIZATIONNAME = "Boris Bügling"; 128 | }; 129 | buildConfigurationList = A1C8AEFC19BB7A760079B96A /* Build configuration list for PBXProject "PuncoverPlugin" */; 130 | compatibilityVersion = "Xcode 3.2"; 131 | developmentRegion = English; 132 | hasScannedForEncodings = 0; 133 | knownRegions = ( 134 | en, 135 | ); 136 | mainGroup = A1C8AEF819BB7A760079B96A; 137 | productRefGroup = A1C8AF0219BB7A770079B96A /* Products */; 138 | projectDirPath = ""; 139 | projectRoot = ""; 140 | targets = ( 141 | A1C8AF0019BB7A760079B96A /* PuncoverPlugin */, 142 | ); 143 | }; 144 | /* End PBXProject section */ 145 | 146 | /* Begin PBXResourcesBuildPhase section */ 147 | A1C8AEFF19BB7A760079B96A /* Resources */ = { 148 | isa = PBXResourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | A1EA312019BBAB5D0088DE5B /* test.json in Resources */, 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXResourcesBuildPhase section */ 156 | 157 | /* Begin PBXSourcesBuildPhase section */ 158 | A1C8AEFD19BB7A760079B96A /* Sources */ = { 159 | isa = PBXSourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | A1EA312319BBABBF0088DE5B /* BBUFunctionStatistics.m in Sources */, 163 | A1C8AF1019BB7A770079B96A /* BBUPuncoverPlugin.m in Sources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXSourcesBuildPhase section */ 168 | 169 | /* Begin XCBuildConfiguration section */ 170 | A1C8AF1219BB7A770079B96A /* Debug */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | ALWAYS_SEARCH_USER_PATHS = NO; 174 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 175 | CLANG_CXX_LIBRARY = "libc++"; 176 | CLANG_ENABLE_MODULES = YES; 177 | CLANG_ENABLE_OBJC_ARC = YES; 178 | CLANG_WARN_BOOL_CONVERSION = YES; 179 | CLANG_WARN_CONSTANT_CONVERSION = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_EMPTY_BODY = YES; 182 | CLANG_WARN_ENUM_CONVERSION = YES; 183 | CLANG_WARN_INT_CONVERSION = YES; 184 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 185 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 186 | COPY_PHASE_STRIP = NO; 187 | GCC_C_LANGUAGE_STANDARD = gnu99; 188 | GCC_DYNAMIC_NO_PIC = NO; 189 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 190 | GCC_OPTIMIZATION_LEVEL = 0; 191 | GCC_PREPROCESSOR_DEFINITIONS = ( 192 | "DEBUG=1", 193 | "$(inherited)", 194 | ); 195 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 196 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 197 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 198 | GCC_WARN_UNDECLARED_SELECTOR = YES; 199 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 200 | GCC_WARN_UNUSED_FUNCTION = YES; 201 | GCC_WARN_UNUSED_VARIABLE = YES; 202 | MACOSX_DEPLOYMENT_TARGET = 10.8; 203 | ONLY_ACTIVE_ARCH = YES; 204 | SDKROOT = macosx; 205 | WARNING_CFLAGS = ( 206 | "-Wno-incomplete-implementation", 207 | "-Wno-undeclared-selector", 208 | ); 209 | }; 210 | name = Debug; 211 | }; 212 | A1C8AF1319BB7A770079B96A /* Release */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_WARN_BOOL_CONVERSION = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 223 | CLANG_WARN_EMPTY_BODY = YES; 224 | CLANG_WARN_ENUM_CONVERSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 227 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 228 | COPY_PHASE_STRIP = YES; 229 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 230 | ENABLE_NS_ASSERTIONS = NO; 231 | GCC_C_LANGUAGE_STANDARD = gnu99; 232 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 233 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 234 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 235 | GCC_WARN_UNDECLARED_SELECTOR = YES; 236 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 237 | GCC_WARN_UNUSED_FUNCTION = YES; 238 | GCC_WARN_UNUSED_VARIABLE = YES; 239 | MACOSX_DEPLOYMENT_TARGET = 10.8; 240 | SDKROOT = macosx; 241 | WARNING_CFLAGS = ( 242 | "-Wno-incomplete-implementation", 243 | "-Wno-undeclared-selector", 244 | ); 245 | }; 246 | name = Release; 247 | }; 248 | A1C8AF1519BB7A770079B96A /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | COMBINE_HIDPI_IMAGES = YES; 252 | DEPLOYMENT_LOCATION = YES; 253 | DSTROOT = "$(HOME)"; 254 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 255 | GCC_PREFIX_HEADER = "Supporting Files/PuncoverPlugin-Prefix.pch"; 256 | INFOPLIST_FILE = "Supporting Files/PuncoverPlugin-Info.plist"; 257 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 258 | PRODUCT_NAME = "$(TARGET_NAME)"; 259 | WRAPPER_EXTENSION = xcplugin; 260 | }; 261 | name = Debug; 262 | }; 263 | A1C8AF1619BB7A770079B96A /* Release */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | COMBINE_HIDPI_IMAGES = YES; 267 | DEPLOYMENT_LOCATION = YES; 268 | DSTROOT = "$(HOME)"; 269 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 270 | GCC_PREFIX_HEADER = "Supporting Files/PuncoverPlugin-Prefix.pch"; 271 | INFOPLIST_FILE = "Supporting Files/PuncoverPlugin-Info.plist"; 272 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 273 | PRODUCT_NAME = "$(TARGET_NAME)"; 274 | WRAPPER_EXTENSION = xcplugin; 275 | }; 276 | name = Release; 277 | }; 278 | /* End XCBuildConfiguration section */ 279 | 280 | /* Begin XCConfigurationList section */ 281 | A1C8AEFC19BB7A760079B96A /* Build configuration list for PBXProject "PuncoverPlugin" */ = { 282 | isa = XCConfigurationList; 283 | buildConfigurations = ( 284 | A1C8AF1219BB7A770079B96A /* Debug */, 285 | A1C8AF1319BB7A770079B96A /* Release */, 286 | ); 287 | defaultConfigurationIsVisible = 0; 288 | defaultConfigurationName = Release; 289 | }; 290 | A1C8AF1419BB7A770079B96A /* Build configuration list for PBXNativeTarget "PuncoverPlugin" */ = { 291 | isa = XCConfigurationList; 292 | buildConfigurations = ( 293 | A1C8AF1519BB7A770079B96A /* Debug */, 294 | A1C8AF1619BB7A770079B96A /* Release */, 295 | ); 296 | defaultConfigurationIsVisible = 0; 297 | defaultConfigurationName = Release; 298 | }; 299 | /* End XCConfigurationList section */ 300 | }; 301 | rootObject = A1C8AEF919BB7A760079B96A /* Project object */; 302 | } 303 | -------------------------------------------------------------------------------- /PuncoverPlugin.xcodeproj/xcshareddata/xcschemes/PuncoverPlugin.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 64 | 66 | 67 | 68 | 70 | 71 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xcode plugin for displaying information in the gutter. 2 | 3 | ![project deprecated](https://img.shields.io/badge/project%20status-deprecated-red.svg) 4 | 5 | This project is deprecated in favor of [Editor Extensions](https://developer.apple.com/videos/play/wwdc2016/414/) support in Xcode 8+. 6 | 7 | ![Screenshot of the plugin in action](screenshots/puncover-plugin.png) 8 | 9 | It will display JSON from *.gutter.json* in the current project's directory, example: 10 | 11 | ```json 12 | { "/some/file": [ 13 | { 14 | "line": 23, 15 | "long_text": "Some longer text in a tooltip when hovering over the line.", 16 | "name": "main", 17 | "short_text": "gutter text", 18 | "background_color":"0x35CC4B" 19 | } 20 | ] } 21 | ``` 22 | 23 | see [this file](Resources/test.json) for more information. This enables the integration of abitrary tools and scripts into Xcode without the need to write yet another plugin. 24 | 25 | ## Current applications 26 | 27 | - Show code coverage information, generated with [slather][5]. If you already have set it up, slather can generate a suitable *.gutter.json* by simply running: 28 | 29 | $ slather coverage -g path/to/project.xcodeproj 30 | 31 | - Show code size information of [Pebble][1] applications, using [puncover][2] - this also inspired the name of this plugin. 32 | 33 | - Show findings of [Faux Pas][3], using [this script][4] to convert the JSON. 34 | 35 | ## Installation 36 | 37 | Either 38 | 39 | - Clone and build the plugin yourself, it will be installed to the right location automatically by building it. 40 | 41 | or 42 | 43 | - Install it via [Alcatraz](http://alcatraz.io/) 44 | 45 | In any case, relaunch Xcode to load it. 46 | 47 | ## Help needed 48 | 49 | Follow [@NeoNacho](https://twitter.com/NeoNacho) to help me beat [@orta](https://twitter.com/orta) in followers count. 50 | 51 | [1]: https://getpebble.com 52 | [2]: https://github.com/HBehrens/puncover 53 | [3]: http://fauxpasapp.com 54 | [4]: https://gist.github.com/neonichu/b172f0afe5ceb58155c3 55 | [5]: https://github.com/venmo/slather 56 | -------------------------------------------------------------------------------- /Resources/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "timestamp": "2014-09-07 11:40:48.343151" 4 | }, 5 | "symbols_by_file": { 6 | "../../../../thomas/work/arm-eabi-toolchain/gcc-4.7-2012.09/libgcc/config/arm/ieee754-df.S": [ 7 | { 8 | "line": 605, 9 | "long_text": "size: 596", 10 | "name": "__aeabi_dmul", 11 | "short_text": "596" 12 | }, 13 | { 14 | "line": 913, 15 | "long_text": "size: 464", 16 | "name": "__aeabi_ddiv", 17 | "short_text": "464" 18 | }, 19 | { 20 | "line": 1344, 21 | "long_text": "size: 64", 22 | "name": "__aeabi_d2uiz", 23 | "short_text": "64" 24 | } 25 | ], 26 | "/Users/boris/Projects/PuncoverPlugin/Code/BBUPuncoverPlugin.m": [ 27 | { 28 | "line": 4, 29 | "long_text": "size: 130", 30 | "name": "__pbl_app_info", 31 | "short_text": "130\nfoobar" 32 | } 33 | ], 34 | "src/puncover.c": [ 35 | { 36 | "line": 8, 37 | "long_text": "size: 36, stack size: 16", 38 | "name": "dynamic_stack2", 39 | "short_text": "36 16" 40 | }, 41 | { 42 | "line": 3, 43 | "long_text": "size: 4", 44 | "name": "used", 45 | "short_text": "4" 46 | }, 47 | { 48 | "line": 25, 49 | "long_text": "size: 52, stack size: 8", 50 | "name": "main", 51 | "short_text": "52 8" 52 | }, 53 | { 54 | "line": 13, 55 | "long_text": "size: 8", 56 | "name": "some_double_value", 57 | "short_text": "8" 58 | } 59 | ] 60 | } 61 | } -------------------------------------------------------------------------------- /Supporting Files/PuncoverPlugin-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | org.vu0.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | DVTPlugInCompatibilityUUIDs 26 | 27 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 28 | FEC992CC-CA4A-4CFD-8881-77300FCB848A 29 | C4A681B0-4A26-480E-93EC-1218098B9AA0 30 | AD68E85B-441B-4301-B564-A45E4919A6AD 31 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 32 | 640F884E-CE55-4B40-87C0-8869546CAB7A 33 | 37B30044-3B14-46BA-ABAA-F01000C27B63 34 | A16FF353-8441-459E-A50C-B071F53F51B7 35 | E969541F-E6F9-4D25-8158-72DC3545A6C6 36 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 37 | E71C2CFE-BFD8-4044-8F06-00AE685A406C 38 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 39 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 40 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 41 | 42 | NSPrincipalClass 43 | BBUPuncoverPlugin 44 | XC4Compatible 45 | 46 | XC5Compatible 47 | 48 | XCGCReady 49 | 50 | XCPluginHasUI 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Supporting Files/PuncoverPlugin-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /screenshots/puncover-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonichu/PuncoverPlugin/276fbd45f642c0cd2cf37f5949a1629ecfe41351/screenshots/puncover-plugin.png --------------------------------------------------------------------------------