├── .gitignore ├── Classes ├── HOPopoverViewController.h ├── HOPopoverViewController.m ├── HOStringFrameView.h ├── HOStringFrameView.m ├── HOStringHelper.h ├── HOStringHelper.m ├── HOStringInfoButton.h └── HOStringInfoButton.m ├── HOStringSense.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── xcshareddata │ └── HOStringSense.xccheckout ├── Info.plist ├── README.md ├── Shortcut.png └── StringDemoAnimation.gif /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.xcodeproj/*.pbxuser 3 | *.xcodeproj/*.perspectivev3 4 | *.xcodeproj/xcuserdata 5 | .DS_Store 6 | .swp 7 | ~.nib 8 | .pbxuser 9 | .perspective 10 | *.perspectivev3 11 | *.mode1v3 12 | *.xcworkspacedata 13 | *.xcuserstate 14 | *xcuserdata* 15 | -------------------------------------------------------------------------------- /Classes/HOPopoverViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import 8 | 9 | @interface HOPopoverViewController : NSViewController { 10 | NSTextField *_textField; 11 | } 12 | 13 | @property (weak, nonatomic) id delegate; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Classes/HOPopoverViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import "HOPopoverViewController.h" 8 | 9 | @implementation HOPopoverViewController 10 | 11 | - (NSView *)view { 12 | if(!_textField) { 13 | _textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 200)]; 14 | _textField.focusRingType = NSFocusRingTypeNone; 15 | _textField.bordered = NO; 16 | _textField.backgroundColor = [NSColor colorWithCalibratedWhite:0.974 alpha:1.000]; 17 | _textField.textColor = [NSColor colorWithCalibratedWhite:0.107 alpha:1.000]; 18 | _textField.delegate = self; 19 | } 20 | return _textField; 21 | } 22 | 23 | - (void)dealloc { 24 | _delegate = nil; 25 | } 26 | 27 | - (void)controlTextDidChange:(NSNotification *)obj { 28 | // NSLog(@"Test: %@", [_textField stringValue]); 29 | if([_delegate respondsToSelector:@selector(stringDidChange:)]) { 30 | [_delegate performSelector:@selector(stringDidChange:) withObject:nil]; 31 | } 32 | } 33 | 34 | - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector { 35 | // NSLog(@"Textview Command: %@", NSStringFromSelector(commandSelector)); 36 | if(commandSelector == @selector(cancelOperation:)) { 37 | if([_delegate respondsToSelector:@selector(dismissPopover)]) { 38 | [_delegate performSelector:@selector(dismissPopover)]; 39 | } 40 | return YES; 41 | } 42 | 43 | if (commandSelector == @selector(insertNewline:)) { 44 | // new line action: 45 | // always insert a line-break character and don’t cause the receiver 46 | // to end editing 47 | [textView insertNewlineIgnoringFieldEditor:self]; 48 | return YES; 49 | } 50 | 51 | return NO; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Classes/HOStringFrameView.h: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import 8 | 9 | @interface HOStringFrameView : NSView 10 | 11 | @property (nonatomic, strong) NSColor *color; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Classes/HOStringFrameView.m: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import "HOStringFrameView.h" 8 | 9 | @implementation HOStringFrameView 10 | 11 | - (void)drawRect:(NSRect)dirtyRect { 12 | [self.color setStroke]; 13 | NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(self.bounds, 0.5, 0.5) xRadius:3.0 yRadius:3.0]; 14 | [path stroke]; 15 | } 16 | 17 | - (void)setColor:(NSColor *)color { 18 | if (color != _color) { 19 | _color = color; 20 | [self setNeedsDisplay:YES]; 21 | } 22 | } 23 | 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Classes/HOStringHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import 8 | #import 9 | 10 | @class HOStringFrameView, HOStringInfoButton, HOPopoverViewController; 11 | 12 | @interface HOStringHelper : NSObject { 13 | HOPopoverViewController *_stringPopoverViewController; 14 | NSPopover *_stringPopover; 15 | NSRegularExpression *_stringRegex; 16 | } 17 | 18 | @property (nonatomic, strong) HOStringInfoButton *stringButton; 19 | @property (nonatomic, strong) HOStringFrameView *stringFrameView; 20 | @property (nonatomic, strong) NSTextView *textView; 21 | @property (nonatomic, assign) NSRange selectedStringRange; 22 | @property (nonatomic, copy) NSString *selectedStringContent; 23 | 24 | - (void)dismissPopover; 25 | - (void)activateColorHighlighting; 26 | - (void)deactivateColorHighlighting; 27 | - (NSString *)stringInText:(NSString *)text selectedRange:(NSRange)selectedRange matchedRange:(NSRangePointer)matchedRange; 28 | 29 | - (NSString *)escapeString:(NSString *)string; 30 | - (NSString *)unescapeString:(NSString *)string; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Classes/HOStringHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import "HOStringHelper.h" 8 | #import "HOStringInfoButton.h" 9 | #import "HOStringFrameView.h" 10 | #import "HOPopoverViewController.h" 11 | 12 | #define kHOStringHelperHighlightingDisabled @"HOStringHelperHighlightingDisabled" 13 | 14 | #define NSNullRange NSMakeRange(NSNotFound, 0) 15 | 16 | @implementation HOStringHelper 17 | 18 | #pragma mark - String Helper 19 | 20 | - (NSString *)escapeString:(NSString *)string { 21 | NSMutableString *result = [NSMutableString string]; 22 | @try { 23 | NSUInteger length = [string length]; 24 | for (NSUInteger i = 0; i < length; i++) { 25 | unichar uc = [string characterAtIndex:i]; 26 | switch (uc) { 27 | case '\"': [result appendString:@"\\\""]; break; 28 | case '\'': [result appendString:@"\\\'"]; break; 29 | case '\\': [result appendString:@"\\\\"]; break; 30 | case '\t': [result appendString:@"\\t"]; break; 31 | case '\n': [result appendString:@"\\n"]; break; 32 | case '\r': [result appendString:@"\\r"]; break; 33 | case '\b': [result appendString:@"\\b"]; break; 34 | case '\f': [result appendString:@"\\f"]; break; 35 | default: { 36 | if (uc < 0x20) { 37 | [result appendFormat:@"\\u%04x", uc]; 38 | } 39 | else { 40 | [result appendFormat:@"%C", uc]; 41 | } 42 | } break; 43 | } 44 | } 45 | // } 46 | } 47 | @catch (NSException *exception) { 48 | NSLog(@"Error while converting string: %@", exception); 49 | } 50 | return (NSString *)result; 51 | } 52 | 53 | #define nextUC ++i; if(i>=length) { break; }; uc = [string characterAtIndex:i]; 54 | - (NSString *)unescapeString:(NSString *)string { 55 | // NSScanner *scanner = [[NSScanner alloc] initWithString:string]; 56 | NSMutableString *result = [NSMutableString string]; 57 | NSUInteger length = [string length]; 58 | for (NSUInteger i = 0; i < length; i++) { 59 | unichar uc = [string characterAtIndex:i]; 60 | if(uc == '\\') { 61 | nextUC; 62 | switch (uc) { 63 | case '\"': [result appendString:@"\""]; break; 64 | case '\'': [result appendString:@"\'"]; break; 65 | case '\\': [result appendString:@"\\"]; break; 66 | case 't': [result appendString:@"\t"]; break; 67 | case 'n': [result appendString:@"\n"]; break; 68 | case 'r': [result appendString:@"\r"]; break; 69 | case 'b': [result appendString:@"\b"]; break; 70 | case 'f': [result appendString:@"\f"]; break; 71 | case 'u': { 72 | unichar hex[5]; hex[4] = 0; 73 | nextUC; hex[0] = uc; 74 | nextUC; hex[1] = uc; 75 | nextUC; hex[2] = uc; 76 | nextUC; hex[3] = uc; 77 | 78 | } break; 79 | default: { 80 | CFStringAppendCharacters((CFMutableStringRef)result, &uc, 1); 81 | } break; 82 | } 83 | } 84 | else { 85 | CFStringAppendCharacters((CFMutableStringRef)result, &uc, 1); 86 | } 87 | } 88 | return result; 89 | } 90 | 91 | //- (NSString *)unescapeString:(NSString *)string { 92 | // @try { 93 | // NSError *error = nil; 94 | // NSString *s = [NSString stringWithFormat:@"\"%@\"", string]; 95 | // NSString *result = [NSJSONSerialization JSONObjectWithData:[s dataUsingEncoding:NSUTF8StringEncoding] 96 | // options:NSJSONReadingAllowFragments 97 | // error:&error]; 98 | // if(!result) { 99 | // NSLog(@"Error while unescaping: %@", error); 100 | // return nil; 101 | // } 102 | // return result; 103 | // } 104 | // @catch (NSException *exception) { ; } 105 | // return nil; 106 | //} 107 | 108 | #pragma mark - Plugin Initialization 109 | 110 | + (void)pluginDidLoad:(NSBundle *)plugin { 111 | static id sharedPlugin = nil; 112 | static dispatch_once_t onceToken; 113 | dispatch_once(&onceToken, ^{ 114 | sharedPlugin = [[self alloc] init]; 115 | }); 116 | } 117 | 118 | - (id)init { 119 | if (self = [super init]) { 120 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:nil]; 121 | _selectedStringRange = NSMakeRange(NSNotFound, 0); 122 | _stringRegex = [NSRegularExpression regularExpressionWithPattern:@"\"((\\\\\"|.)*?)\"" 123 | options:0 124 | error:NULL]; 125 | } 126 | return self; 127 | } 128 | 129 | - (void)applicationDidFinishLaunching:(NSNotification *)notification { 130 | NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"]; 131 | if (editMenuItem) { 132 | [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]]; 133 | 134 | { 135 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Enable Strings Popover" action:@selector(toggleColorHighlightingEnabled:) keyEquivalent:@""]; 136 | [item setTarget:self]; 137 | [[editMenuItem submenu] addItem:item]; 138 | } 139 | 140 | { 141 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Show Strings Popover" action:@selector(showPopover:) keyEquivalent:@""]; 142 | [item setTarget:self]; 143 | [[editMenuItem submenu] addItem:item]; 144 | } 145 | 146 | // NSMenuItem *insertColorMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Insert Color..." action:@selector(insertColor:) keyEquivalent:@""] autorelease]; 147 | // [insertColorMenuItem setTarget:self]; 148 | // [[editMenuItem submenu] addItem:insertColorMenuItem]; 149 | } 150 | 151 | BOOL highlightingEnabled = ![[NSUserDefaults standardUserDefaults] boolForKey:kHOStringHelperHighlightingDisabled]; 152 | if (highlightingEnabled) { 153 | [self activateColorHighlighting]; 154 | } 155 | } 156 | 157 | #pragma mark - Preferences 158 | 159 | - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { 160 | if ([menuItem action] == @selector(showPopover:)) { 161 | return ![_stringPopover isShown]; 162 | } 163 | else if ([menuItem action] == @selector(toggleColorHighlightingEnabled:)) { 164 | BOOL enabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHOStringHelperHighlightingDisabled]; 165 | [menuItem setState:enabled ? NSOffState : NSOnState]; 166 | return YES; 167 | } 168 | return YES; 169 | } 170 | 171 | - (void)toggleColorHighlightingEnabled:(id)sender { 172 | BOOL enabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHOStringHelperHighlightingDisabled]; 173 | [[NSUserDefaults standardUserDefaults] setBool:!enabled forKey:kHOStringHelperHighlightingDisabled]; 174 | if (enabled) { 175 | [self activateColorHighlighting]; 176 | } else { 177 | [self deactivateColorHighlighting]; 178 | } 179 | } 180 | 181 | - (void)activateColorHighlighting { 182 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectionDidChange:) name:NSTextViewDidChangeSelectionNotification object:nil]; 183 | if (!self.textView) { 184 | NSResponder *firstResponder = [[NSApp keyWindow] firstResponder]; 185 | if ([firstResponder isKindOfClass:NSClassFromString(@"DVTSourceTextView")] && [firstResponder isKindOfClass:[NSTextView class]]) { 186 | self.textView = (NSTextView *)firstResponder; 187 | } 188 | } 189 | if (self.textView) { 190 | NSNotification *notification = [NSNotification notificationWithName:NSTextViewDidChangeSelectionNotification object:self.textView]; 191 | [self selectionDidChange:notification]; 192 | 193 | } 194 | } 195 | 196 | - (void)deactivateColorHighlighting { 197 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextViewDidChangeSelectionNotification object:nil]; 198 | [self dismissPopover]; 199 | self.textView = nil; 200 | } 201 | 202 | #pragma mark - Text Selection Handling 203 | 204 | - (void)selectionDidChange:(NSNotification *)notification { 205 | // NSLog(@"%s", __PRETTY_FUNCTION__); 206 | if ([[notification object] isKindOfClass:NSClassFromString(@"DVTSourceTextView")] && [[notification object] isKindOfClass:[NSTextView class]]) { 207 | self.textView = (NSTextView *)[notification object]; 208 | BOOL disabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHOStringHelperHighlightingDisabled]; 209 | if (disabled) { 210 | return; 211 | } 212 | NSArray *selectedRanges = [self.textView selectedRanges]; 213 | if (selectedRanges.count >= 1) { 214 | NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue]; 215 | NSString *text = self.textView.textStorage.string; 216 | NSRange lineRange = [text lineRangeForRange:selectedRange]; 217 | NSRange selectedRangeInLine = NSMakeRange(selectedRange.location - lineRange.location, selectedRange.length); 218 | NSString *line = [text substringWithRange:lineRange]; 219 | NSRange stringRange = NSNullRange; 220 | 221 | self.selectedStringContent = [self stringInText:line selectedRange:selectedRangeInLine matchedRange:&stringRange]; 222 | if (_selectedStringContent && [_selectedStringContent length] >= 2) { 223 | 224 | // String's content 225 | NSInteger oldLocation = _selectedStringRange.location; 226 | self.selectedStringContent = [_selectedStringContent substringWithRange:NSMakeRange(1, _selectedStringContent.length - 2)]; 227 | self.selectedStringRange = NSMakeRange(stringRange.location + lineRange.location, stringRange.length); 228 | if(oldLocation != _selectedStringRange.location) { 229 | [self dismissPopover]; 230 | } 231 | 232 | // Color calculations based ion Xcode theme 233 | NSColor *backgroundColor = [self.textView.backgroundColor colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]]; 234 | CGFloat r = 1.0; CGFloat g = 1.0; CGFloat b = 1.0; 235 | [backgroundColor getRed:&r green:&g blue:&b alpha:NULL]; 236 | CGFloat backgroundLuminance = (r + g + b) / 3.0; 237 | NSColor *strokeColor = (backgroundLuminance > 0.5) ? [NSColor colorWithCalibratedWhite:0.5 alpha:1] : [NSColor colorWithCalibratedWhite:1.000 alpha:1]; 238 | 239 | // Button's label 240 | NSString * aString = [NSString stringWithFormat:@"%d", (int)[[self unescapeString:_selectedStringContent] length]]; 241 | NSMutableDictionary * aAttributes = [NSMutableDictionary dictionary]; 242 | NSMutableParagraphStyle * aStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 243 | aStyle.alignment = NSCenterTextAlignment; 244 | [aAttributes setValue:(backgroundLuminance > 0.5) ? [NSColor whiteColor] : [NSColor blackColor] 245 | forKey:NSForegroundColorAttributeName]; 246 | [aAttributes setValue:[NSFont boldSystemFontOfSize:11] forKey:NSFontAttributeName]; 247 | [aAttributes setValue:aStyle forKey:NSParagraphStyleAttributeName]; 248 | NSAttributedString * aAttributedString = [[NSAttributedString alloc] initWithString:aString attributes:aAttributes]; 249 | self.stringButton.attributedTitle = aAttributedString; 250 | self.stringButton.strokeColor = strokeColor; 251 | 252 | // Place button 253 | NSRect selectionRectOnScreen = [self.textView firstRectForCharacterRange:self.selectedStringRange]; 254 | NSRect selectionRectInWindow = [self.textView.window convertRectFromScreen:selectionRectOnScreen]; 255 | NSRect selectionRectInView = [self.textView convertRect:selectionRectInWindow fromView:nil]; 256 | 257 | CGFloat Width = [aAttributedString size].width + 14; 258 | NSRect buttonRect = NSMakeRect(NSMinX(selectionRectInView), NSMinY(selectionRectInView) - selectionRectInView.size.height - 2, Width, selectionRectInView.size.height); 259 | self.stringButton.frame = NSIntegralRect(buttonRect); 260 | 261 | 262 | [self.textView addSubview:self.stringButton]; 263 | 264 | // Draw the frame around the string 265 | self.stringFrameView.frame = NSInsetRect(NSIntegralRect(selectionRectInView), -1, -1); 266 | self.stringFrameView.color = strokeColor; 267 | [self.textView addSubview:self.stringFrameView]; 268 | 269 | return; 270 | } 271 | } 272 | [self removeSelection]; 273 | } 274 | } 275 | 276 | - (void)dismissPopover { 277 | // NSLog(@"%s", __PRETTY_FUNCTION__); 278 | if(_stringPopover) { 279 | [_stringPopover close]; 280 | } 281 | } 282 | 283 | - (void)removeSelection { 284 | // NSLog(@"%s", __PRETTY_FUNCTION__); 285 | [self dismissPopover]; 286 | [self.stringButton removeFromSuperview]; 287 | [self.stringFrameView removeFromSuperview]; 288 | self.selectedStringRange = NSNullRange; 289 | self.selectedStringContent = nil; 290 | } 291 | 292 | - (void)stringDidChange:(id)sender { 293 | // NSLog(@"%s", __PRETTY_FUNCTION__); 294 | if (self.selectedStringRange.location == NSNotFound) { 295 | return; 296 | } 297 | NSTextField *textfield = (id)_stringPopoverViewController.view; 298 | NSString *result = textfield.stringValue; 299 | if(result) { 300 | result = [self escapeString:result]; 301 | if(![result isEqualToString:_selectedStringContent]) { 302 | [self.textView.undoManager beginUndoGrouping]; 303 | [self.textView insertText:[NSString stringWithFormat:@"%@", result] 304 | replacementRange:self.selectedStringRange]; 305 | [self.textView.undoManager endUndoGrouping]; 306 | } 307 | } 308 | } 309 | 310 | //- (void)popoverWillClose:(NSNotification *)notification { 311 | // NSLog(@"%s", __PRETTY_FUNCTION__); 312 | // // [self stringDidChange:nil]; 313 | //} 314 | 315 | - (void)showPopover:(id)sender { 316 | // NSLog(@"%s", __PRETTY_FUNCTION__); 317 | if(_selectedStringRange.location == NSNotFound) { 318 | return; 319 | } 320 | [self dismissPopover]; 321 | if(!_stringPopoverViewController) { 322 | _stringPopoverViewController = [[HOPopoverViewController alloc] init]; 323 | _stringPopoverViewController.delegate = self; 324 | } 325 | NSTextField *textfield = (id)_stringPopoverViewController.view; 326 | textfield.stringValue = [self unescapeString:_selectedStringContent]; 327 | textfield.font = self.textView.font; 328 | NSSize size = NSMakeSize(self.textView.bounds.size.width * 0.50, 120); 329 | if(!_stringPopover) { 330 | _stringPopover = [[NSPopover alloc] init]; 331 | } 332 | _stringPopover.contentViewController = _stringPopoverViewController; 333 | _stringPopover.contentSize = size; 334 | _stringPopover.delegate = self; 335 | [_stringPopover showRelativeToRect:self.stringButton.bounds 336 | ofView:self.stringButton 337 | preferredEdge:NSMinYEdge]; 338 | } 339 | 340 | #pragma mark - View Initialization 341 | 342 | - (HOStringInfoButton *)stringButton { 343 | if (!_stringButton) { 344 | _stringButton = [[HOStringInfoButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 30)]; 345 | [_stringButton setTarget:self]; 346 | [_stringButton setAction:@selector(showPopover:)]; 347 | } 348 | return _stringButton; 349 | } 350 | 351 | - (HOStringFrameView *)stringFrameView { 352 | if (!_stringFrameView) { 353 | _stringFrameView = [[HOStringFrameView alloc] initWithFrame:NSZeroRect]; 354 | } 355 | return _stringFrameView; 356 | } 357 | 358 | #pragma mark - Color String Parsing 359 | 360 | - (NSString *)stringInText:(NSString *)text selectedRange:(NSRange)selectedRange matchedRange:(NSRangePointer)matchedRange { 361 | __block NSString *foundStringContent = nil; 362 | __block NSRange foundColorRange = NSMakeRange(NSNotFound, 0); 363 | [_stringRegex enumerateMatchesInString:text options:0 range:NSMakeRange(0, text.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { 364 | NSRange colorRange = [result range]; 365 | if (selectedRange.location >= colorRange.location + 1 && NSMaxRange(selectedRange) <= NSMaxRange(colorRange) - 1) { 366 | foundStringContent = [text substringWithRange:[result rangeAtIndex:0]]; 367 | colorRange.location++; 368 | colorRange.length -= 2; 369 | foundColorRange = colorRange; 370 | *stop = YES; 371 | } 372 | }]; 373 | if (foundStringContent) { 374 | if (matchedRange != NULL) { 375 | *matchedRange = foundColorRange; 376 | } 377 | return foundStringContent; 378 | } 379 | return nil; 380 | } 381 | 382 | #pragma mark - 383 | 384 | - (void)dealloc { 385 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 386 | [self dismissPopover]; 387 | } 388 | 389 | @end 390 | -------------------------------------------------------------------------------- /Classes/HOStringInfoButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import 8 | 9 | @interface HOStringInfoButton : NSButton 10 | 11 | @property (copy, nonatomic) NSColor *strokeColor; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Classes/HOStringInfoButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // HOStringSense by Dirk Holtwick 2012, holtwick.it 3 | // Based on OMColorSense by by Ole Zorn, 2012 4 | // Licensed under BSD style license 5 | // 6 | 7 | #import "HOStringInfoButton.h" 8 | 9 | @implementation HOStringInfoButton 10 | 11 | - (id)initWithFrame:(NSRect)frameRect { 12 | if(self = [super initWithFrame:frameRect]) { 13 | self.font = [NSFont boldSystemFontOfSize:11.]; 14 | self.bordered = NO; 15 | } 16 | return self; 17 | } 18 | 19 | - (void)drawRect:(NSRect)dirtyRect { 20 | [NSGraphicsContext saveGraphicsState]; 21 | NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0, 0, self.bounds.size.width, self.bounds.size.height ) xRadius:3.0 yRadius:3.0]; 22 | [path addClip]; 23 | if (self.strokeColor) { 24 | [self.strokeColor set]; 25 | } 26 | else { 27 | [[NSColor colorWithCalibratedWhite:0.500 alpha:0.500] setFill]; 28 | } 29 | [path fill]; 30 | [super drawRect:dirtyRect]; 31 | 32 | // [self drawWellInside:self.bounds]; 33 | [NSGraphicsContext restoreGraphicsState]; 34 | } 35 | 36 | - (void)setStrokeColor:(NSColor *)strokeColor { 37 | if (strokeColor != _strokeColor) { 38 | _strokeColor = strokeColor; 39 | [self setNeedsDisplay:YES]; 40 | } 41 | } 42 | 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /HOStringSense.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 43BA8625167490550002C238 /* HOPopoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 43BA8624167490550002C238 /* HOPopoverViewController.m */; }; 11 | 7F2EB89C145057F200E97A87 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F2EB899145057EA00E97A87 /* AppKit.framework */; }; 12 | 7F6EE2BF15FA6F3B00BA114A /* HOStringFrameView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6EE2BE15FA6F3B00BA114A /* HOStringFrameView.m */; }; 13 | 7FDADE3A15FA6CA400A847E3 /* HOStringInfoButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FDADE3915FA6CA400A847E3 /* HOStringInfoButton.m */; }; 14 | DA1B5D020E64686800921439 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 089C1672FE841209C02AAC07 /* Foundation.framework */; }; 15 | DA37E2DC0E6291C8001BDFEF /* HOStringHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DA37E2DB0E6291C8001BDFEF /* HOStringHelper.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXBuildRule section */ 19 | 4302EDD316748CAD001E6539 /* PBXBuildRule */ = { 20 | isa = PBXBuildRule; 21 | compilerSpec = com.apple.compilers.proxy.script; 22 | fileType = file.xib; 23 | isEditable = 1; 24 | outputFiles = ( 25 | ); 26 | script = "# ibtool\n"; 27 | }; 28 | /* End PBXBuildRule section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 089C1672FE841209C02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 32 | 43BA8623167490550002C238 /* HOPopoverViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HOPopoverViewController.h; sourceTree = ""; }; 33 | 43BA8624167490550002C238 /* HOPopoverViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HOPopoverViewController.m; sourceTree = ""; }; 34 | 43ED5411167509CC00AE1269 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 35 | 7F2B355F15FA59D000DB3249 /* HOStringHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HOStringHelper.h; sourceTree = ""; }; 36 | 7F2EB899145057EA00E97A87 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 37 | 7F6EE2BD15FA6F3B00BA114A /* HOStringFrameView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HOStringFrameView.h; sourceTree = ""; }; 38 | 7F6EE2BE15FA6F3B00BA114A /* HOStringFrameView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HOStringFrameView.m; sourceTree = ""; }; 39 | 7FDADE3815FA6CA400A847E3 /* HOStringInfoButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HOStringInfoButton.h; sourceTree = ""; }; 40 | 7FDADE3915FA6CA400A847E3 /* HOStringInfoButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HOStringInfoButton.m; sourceTree = ""; }; 41 | 8D5B49B6048680CD000E48DA /* HOStringSense.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HOStringSense.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 8D5B49B7048680CD000E48DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | DA37E2DB0E6291C8001BDFEF /* HOStringHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HOStringHelper.m; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 8D5B49B3048680CD000E48DA /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | DA1B5D020E64686800921439 /* Foundation.framework in Frameworks */, 52 | 7F2EB89C145057F200E97A87 /* AppKit.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 089C166AFE841209C02AAC07 /* QuietXcode */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 43ED5411167509CC00AE1269 /* README.md */, 63 | 7F411B0C15FABAC6002F77B6 /* Classes */, 64 | 089C167CFE841241C02AAC07 /* Resources */, 65 | 089C1671FE841209C02AAC07 /* Frameworks and Libraries */, 66 | 19C28FB8FE9D52D311CA2CBB /* Products */, 67 | ); 68 | indentWidth = 4; 69 | name = QuietXcode; 70 | sourceTree = ""; 71 | }; 72 | 089C1671FE841209C02AAC07 /* Frameworks and Libraries */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 7F2EB899145057EA00E97A87 /* AppKit.framework */, 76 | 089C1672FE841209C02AAC07 /* Foundation.framework */, 77 | ); 78 | name = "Frameworks and Libraries"; 79 | sourceTree = ""; 80 | }; 81 | 089C167CFE841241C02AAC07 /* Resources */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 8D5B49B7048680CD000E48DA /* Info.plist */, 85 | ); 86 | name = Resources; 87 | sourceTree = ""; 88 | }; 89 | 19C28FB8FE9D52D311CA2CBB /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 8D5B49B6048680CD000E48DA /* HOStringSense.xcplugin */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | 7F411B0C15FABAC6002F77B6 /* Classes */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 7F2B355F15FA59D000DB3249 /* HOStringHelper.h */, 101 | DA37E2DB0E6291C8001BDFEF /* HOStringHelper.m */, 102 | 7F6EE2BD15FA6F3B00BA114A /* HOStringFrameView.h */, 103 | 7F6EE2BE15FA6F3B00BA114A /* HOStringFrameView.m */, 104 | 7FDADE3815FA6CA400A847E3 /* HOStringInfoButton.h */, 105 | 7FDADE3915FA6CA400A847E3 /* HOStringInfoButton.m */, 106 | 43BA8623167490550002C238 /* HOPopoverViewController.h */, 107 | 43BA8624167490550002C238 /* HOPopoverViewController.m */, 108 | ); 109 | path = Classes; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXNativeTarget section */ 115 | 8D5B49AC048680CD000E48DA /* HOStringSense */ = { 116 | isa = PBXNativeTarget; 117 | buildConfigurationList = 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "HOStringSense" */; 118 | buildPhases = ( 119 | 8D5B49B1048680CD000E48DA /* Sources */, 120 | 4302EDD516748D08001E6539 /* Resources */, 121 | 8D5B49B3048680CD000E48DA /* Frameworks */, 122 | ); 123 | buildRules = ( 124 | 4302EDD316748CAD001E6539 /* PBXBuildRule */, 125 | ); 126 | dependencies = ( 127 | ); 128 | name = HOStringSense; 129 | productInstallPath = "$(HOME)/Library/Bundles"; 130 | productName = QuietXcode; 131 | productReference = 8D5B49B6048680CD000E48DA /* HOStringSense.xcplugin */; 132 | productType = "com.apple.product-type.bundle"; 133 | }; 134 | /* End PBXNativeTarget section */ 135 | 136 | /* Begin PBXProject section */ 137 | 089C1669FE841209C02AAC07 /* Project object */ = { 138 | isa = PBXProject; 139 | attributes = { 140 | LastTestingUpgradeCheck = 0710; 141 | LastUpgradeCheck = 0730; 142 | }; 143 | buildConfigurationList = 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "HOStringSense" */; 144 | compatibilityVersion = "Xcode 3.2"; 145 | developmentRegion = English; 146 | hasScannedForEncodings = 1; 147 | knownRegions = ( 148 | English, 149 | Japanese, 150 | French, 151 | German, 152 | en, 153 | ); 154 | mainGroup = 089C166AFE841209C02AAC07 /* QuietXcode */; 155 | projectDirPath = ""; 156 | projectRoot = ""; 157 | targets = ( 158 | 8D5B49AC048680CD000E48DA /* HOStringSense */, 159 | ); 160 | }; 161 | /* End PBXProject section */ 162 | 163 | /* Begin PBXResourcesBuildPhase section */ 164 | 4302EDD516748D08001E6539 /* Resources */ = { 165 | isa = PBXResourcesBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | /* End PBXResourcesBuildPhase section */ 172 | 173 | /* Begin PBXSourcesBuildPhase section */ 174 | 8D5B49B1048680CD000E48DA /* Sources */ = { 175 | isa = PBXSourcesBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | DA37E2DC0E6291C8001BDFEF /* HOStringHelper.m in Sources */, 179 | 7FDADE3A15FA6CA400A847E3 /* HOStringInfoButton.m in Sources */, 180 | 7F6EE2BF15FA6F3B00BA114A /* HOStringFrameView.m in Sources */, 181 | 43BA8625167490550002C238 /* HOPopoverViewController.m in Sources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXSourcesBuildPhase section */ 186 | 187 | /* Begin XCBuildConfiguration section */ 188 | 1DEB913B08733D840010E9CD /* Debug */ = { 189 | isa = XCBuildConfiguration; 190 | buildSettings = { 191 | ALWAYS_SEARCH_USER_PATHS = NO; 192 | CLANG_ENABLE_OBJC_ARC = YES; 193 | COMBINE_HIDPI_IMAGES = YES; 194 | COPY_PHASE_STRIP = NO; 195 | DEPLOYMENT_LOCATION = YES; 196 | DEPLOYMENT_POSTPROCESSING = YES; 197 | DSTROOT = "$(HOME)"; 198 | GCC_DYNAMIC_NO_PIC = NO; 199 | GCC_MODEL_TUNING = G5; 200 | GCC_OPTIMIZATION_LEVEL = 0; 201 | INFOPLIST_FILE = Info.plist; 202 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 203 | LD_RUNPATH_SEARCH_PATHS = /Developer; 204 | PRODUCT_BUNDLE_IDENTIFIER = "it.holtwick.${PRODUCT_NAME:identifier}"; 205 | PRODUCT_NAME = HOStringSense; 206 | STRIP_INSTALLED_PRODUCT = NO; 207 | WRAPPER_EXTENSION = xcplugin; 208 | }; 209 | name = Debug; 210 | }; 211 | 1DEB913F08733D840010E9CD /* Debug */ = { 212 | isa = XCBuildConfiguration; 213 | buildSettings = { 214 | CURRENT_PROJECT_VERSION = 1.0.1; 215 | ENABLE_TESTABILITY = YES; 216 | GCC_C_LANGUAGE_STANDARD = c99; 217 | GCC_OPTIMIZATION_LEVEL = 0; 218 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 219 | GCC_WARN_UNUSED_VARIABLE = YES; 220 | INSTALL_PATH = "$(HOME)/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 221 | MACOSX_DEPLOYMENT_TARGET = 10.7; 222 | ONLY_ACTIVE_ARCH = YES; 223 | SDKROOT = macosx; 224 | }; 225 | name = Debug; 226 | }; 227 | /* End XCBuildConfiguration section */ 228 | 229 | /* Begin XCConfigurationList section */ 230 | 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "HOStringSense" */ = { 231 | isa = XCConfigurationList; 232 | buildConfigurations = ( 233 | 1DEB913B08733D840010E9CD /* Debug */, 234 | ); 235 | defaultConfigurationIsVisible = 0; 236 | defaultConfigurationName = Debug; 237 | }; 238 | 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "HOStringSense" */ = { 239 | isa = XCConfigurationList; 240 | buildConfigurations = ( 241 | 1DEB913F08733D840010E9CD /* Debug */, 242 | ); 243 | defaultConfigurationIsVisible = 0; 244 | defaultConfigurationName = Debug; 245 | }; 246 | /* End XCConfigurationList section */ 247 | }; 248 | rootObject = 089C1669FE841209C02AAC07 /* Project object */; 249 | } 250 | -------------------------------------------------------------------------------- /HOStringSense.xcodeproj/project.xcworkspace/xcshareddata/HOStringSense.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | E822DE2D-8AFB-45D6-B909-ED96F189E2AE 9 | IDESourceControlProjectName 10 | project 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | F05946858516928A212830FAEE4FD23BEA3A31AB 14 | github.com:holtwick/HOStringSense-for-Xcode.git 15 | 16 | IDESourceControlProjectPath 17 | HOStringSense.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | F05946858516928A212830FAEE4FD23BEA3A31AB 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:holtwick/HOStringSense-for-Xcode.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | F05946858516928A212830FAEE4FD23BEA3A31AB 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | F05946858516928A212830FAEE4FD23BEA3A31AB 36 | IDESourceControlWCCName 37 | HOStringSense-for-Xcode 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | ${CURRENT_PROJECT_VERSION} 19 | CFBundleVersion 20 | ${CURRENT_PROJECT_VERSION} 21 | DVTPlugInCompatibilityUUIDs 22 | 23 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 24 | AD68E85B-441B-4301-B564-A45E4919A6AD 25 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 26 | 640F884E-CE55-4B40-87C0-8869546CAB7A 27 | 63FC1C47-140D-42B0-BB4D-A10B2D225574 28 | 37B30044-3B14-46BA-ABAA-F01000C27B63 29 | C4A681B0-4A26-480E-93EC-1218098B9AA0 30 | A16FF353-8441-459E-A50C-B071F53F51B7 31 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 32 | E969541F-E6F9-4D25-8158-72DC3545A6C6 33 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 34 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 35 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 36 | CC0D0F4F-05B3-431A-8F33-F84AFCB2C651 37 | 7265231C-39B4-402C-89E1-16167C4CC990 38 | F41BD31E-2683-44B8-AE7F-5F09E919790E 39 | 40 | NSPrincipalClass 41 | HOStringHelper 42 | XC4Compatible 43 | 44 | XCGCReady 45 | 46 | XCPluginHasUI 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # String Editing Plugin for Xcode 2 | 3 | Perfect for editing regular expressions, multi line texts, inline HTML and many more use cases. Also provides quick feedback on string length. 4 | 5 | ![Screenshot](https://github.com/holtwick/HOStringSense-for-Xcode/raw/master/StringDemoAnimation.gif "Demo") 6 | 7 | ## Installation 8 | 9 | There are two ways: 10 | 11 | 1. Build the Xcode project and restart Xcode. The plugin will automatically be installed in `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins`. First remove the plugin from there (and restart Xcode). 12 | 13 | If you get a "Permission Denied" error while building, please see [this issue](https://github.com/omz/ColorSense-for-Xcode/issues/1). 14 | 15 | 1. Use [**Alcatraz**, the package manager for Xcode](http://alcatraz.io/) (Attention, you'll need to have MacOS 10.9 installed!) 16 | 17 | ## Keyboard Shortcut 18 | 19 | With a little trick you can enable keyboard shortcut for showing the popover. Open `System Preferences` app, then section `Keyboard` and add a custom application shortcut for Xcode as in the following image: 20 | 21 | ![Screenshot](https://github.com/holtwick/HOStringSense-for-Xcode/raw/master/Shortcut.png "Keyboard Shortcur") 22 | 23 | ## Author 24 | 25 | I'm a Mac and iOS developer, follow me on Twitter [@holtwick](https://twitter.com/holtwick) 26 | 27 | ## Credits 28 | 29 | This work is derived from the awesome [Color Sense](https://github.com/omz/ColorSense-for-Xcode) plugin of Ole Zorn. Thanks Ole! 30 | 31 | ## License 32 | 33 | Copyright (c) 2012-2015, Dirk Holtwick 34 | All rights reserved. 35 | 36 | Redistribution and use in source and binary forms, with or without 37 | modification, are permitted provided that the following conditions are met: 38 | 39 | * Redistributions of source code must retain the above copyright notice, this 40 | list of conditions and the following disclaimer. 41 | 42 | * Redistributions in binary form must reproduce the above copyright notice, 43 | this list of conditions and the following disclaimer in the documentation 44 | and/or other materials provided with the distribution. 45 | 46 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 47 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 48 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 49 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 50 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 51 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 52 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 53 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 54 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 55 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holtwick/HOStringSense-for-Xcode/a067254288ae1bcbfface1506120ea758c0b42c3/Shortcut.png -------------------------------------------------------------------------------- /StringDemoAnimation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holtwick/HOStringSense-for-Xcode/a067254288ae1bcbfface1506120ea758c0b42c3/StringDemoAnimation.gif --------------------------------------------------------------------------------