Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit b798ec4988f5a79f217ee271962360a2a7ba2bea Ole Zorn committed Sep 18, 2012
Showing with 617 additions and 0 deletions.
  1. +14 −0 .gitignore
  2. +13 −0 Classes/JRSwizzle.h
  3. +134 −0 Classes/JRSwizzle.m
  4. +18 −0 Classes/OMQuickHelpPlugin.h
  5. +153 −0 Classes/OMQuickHelpPlugin.m
  6. +30 −0 Info.plist
  7. +206 −0 OMQuickHelp.xcodeproj/project.pbxproj
  8. +49 −0 README.md
@@ -0,0 +1,14 @@
+build
+*.xcodeproj/*.pbxuser
+*.xcodeproj/*.perspectivev3
+*.xcodeproj/xcuserdata
+.DS_Store
+.swp
+~.nib
+.pbxuser
+.perspective
+*.perspectivev3
+*.mode1v3
+*.xcworkspacedata
+*.xcuserstate
+*xcuserdata*
@@ -0,0 +1,13 @@
+// JRSwizzle.h semver:1.0
+// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
+// Some rights reserved: http://opensource.org/licenses/MIT
+// https://github.com/rentzsch/jrswizzle
+
+#import <Foundation/Foundation.h>
+
+@interface NSObject (JRSwizzle)
+
++ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_;
++ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_;
+
+@end
@@ -0,0 +1,134 @@
+// JRSwizzle.m semver:1.0
+// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
+// Some rights reserved: http://opensource.org/licenses/MIT
+// https://github.com/rentzsch/jrswizzle
+
+#import "JRSwizzle.h"
+
+#if TARGET_OS_IPHONE
+ #import <objc/runtime.h>
+ #import <objc/message.h>
+#else
+ #import <objc/objc-class.h>
+#endif
+
+#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \
+ if (ERROR_VAR) { \
+ NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
+ *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
+ code:-1 \
+ userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
+ }
+#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)
+
+#if OBJC_API_VERSION >= 2
+#define GetClass(obj) object_getClass(obj)
+#else
+#define GetClass(obj) (obj ? obj->isa : Nil)
+#endif
+
+@implementation NSObject (JRSwizzle)
+
++ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ {
+#if OBJC_API_VERSION >= 2
+ Method origMethod = class_getInstanceMethod(self, origSel_);
+ if (!origMethod) {
+#if TARGET_OS_IPHONE
+ SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
+#else
+ SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
+#endif
+ return NO;
+ }
+
+ Method altMethod = class_getInstanceMethod(self, altSel_);
+ if (!altMethod) {
+#if TARGET_OS_IPHONE
+ SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
+#else
+ SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
+#endif
+ return NO;
+ }
+
+ class_addMethod(self,
+ origSel_,
+ class_getMethodImplementation(self, origSel_),
+ method_getTypeEncoding(origMethod));
+ class_addMethod(self,
+ altSel_,
+ class_getMethodImplementation(self, altSel_),
+ method_getTypeEncoding(altMethod));
+
+ method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
+ return YES;
+#else
+ // Scan for non-inherited methods.
+ Method directOriginalMethod = NULL, directAlternateMethod = NULL;
+
+ void *iterator = NULL;
+ struct objc_method_list *mlist = class_nextMethodList(self, &iterator);
+ while (mlist) {
+ int method_index = 0;
+ for (; method_index < mlist->method_count; method_index++) {
+ if (mlist->method_list[method_index].method_name == origSel_) {
+ assert(!directOriginalMethod);
+ directOriginalMethod = &mlist->method_list[method_index];
+ }
+ if (mlist->method_list[method_index].method_name == altSel_) {
+ assert(!directAlternateMethod);
+ directAlternateMethod = &mlist->method_list[method_index];
+ }
+ }
+ mlist = class_nextMethodList(self, &iterator);
+ }
+
+ // If either method is inherited, copy it up to the target class to make it non-inherited.
+ if (!directOriginalMethod || !directAlternateMethod) {
+ Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL;
+ if (!directOriginalMethod) {
+ inheritedOriginalMethod = class_getInstanceMethod(self, origSel_);
+ if (!inheritedOriginalMethod) {
+ SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
+ return NO;
+ }
+ }
+ if (!directAlternateMethod) {
+ inheritedAlternateMethod = class_getInstanceMethod(self, altSel_);
+ if (!inheritedAlternateMethod) {
+ SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
+ return NO;
+ }
+ }
+
+ int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1;
+ struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1)));
+ hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind
+ hoisted_method_list->method_count = hoisted_method_count;
+ Method hoisted_method = hoisted_method_list->method_list;
+
+ if (!directOriginalMethod) {
+ bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method));
+ directOriginalMethod = hoisted_method++;
+ }
+ if (!directAlternateMethod) {
+ bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method));
+ directAlternateMethod = hoisted_method;
+ }
+ class_addMethods(self, hoisted_method_list);
+ }
+
+ // Swizzle.
+ IMP temp = directOriginalMethod->method_imp;
+ directOriginalMethod->method_imp = directAlternateMethod->method_imp;
+ directAlternateMethod->method_imp = temp;
+
+ return YES;
+#endif
+}
+
++ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ {
+ return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_];
+}
+
+@end
@@ -0,0 +1,18 @@
+//
+// OMColorHelper.h
+// OMColorHelper
+//
+// Created by Ole Zorn on 09/07/12.
+//
+//
+
+#import <Foundation/Foundation.h>
+#import <AppKit/AppKit.h>
+
+@interface OMQuickHelpPlugin : NSObject {
+
+}
+
+- (void)toggleOpenInDashEnabled:(id)sender;
+
+@end
@@ -0,0 +1,153 @@
+//
+// OMColorHelper.m
+// OMColorHelper
+//
+// Created by Ole Zorn on 09/07/12.
+//
+//
+
+#import "OMQuickHelpPlugin.h"
+#import "JRSwizzle.h"
+
+#define kOMSuppressDashNotInstalledWarning @"OMSuppressDashNotInstalledWarning"
+#define kOMOpenInDashDisabled @"OMOpenInDashDisabled"
+
+@interface NSObject (OMSwizzledIDESourceCodeEditor)
+
+- (void)om_textView:(id)arg1 didClickOnTemporaryLinkAtCharacterIndex:(unsigned long long)arg2 event:(id)arg3 isAltEvent:(BOOL)arg4;
+- (void)om_showQuickHelp:(id)sender;
+- (void)om_dashNotInstalledFallback;
+- (BOOL)om_showQuickHelpForSearchString:(NSString *)searchString;
+
+@end
+
+@implementation NSObject (OMSwizzledIDESourceCodeEditor)
+
+- (void)om_showQuickHelp:(id)sender
+{
+ @try {
+ BOOL dashDisabled = [[NSUserDefaults standardUserDefaults] boolForKey:kOMOpenInDashDisabled];
+ if (dashDisabled) {
+ //No, this is not an infinite loop because the method is swizzled:
+ [self om_showQuickHelp:sender];
+ return;
+ }
+ NSString *symbolString = [self valueForKeyPath:@"selectedExpression.symbolString"];
+ BOOL dashOpened = [self om_showQuickHelpForSearchString:symbolString];
+ if (!dashOpened) {
+ [self om_dashNotInstalledFallback];
+ }
+ }
+ @catch (NSException *exception) {
+
+ }
+}
+
+- (void)om_dashNotInstalledFallback
+{
+ //Fall back to default behavior:
+ [self om_showQuickHelp:self];
+ //Show a warning that Dash is not installed:
+ BOOL showNotInstalledWarning = ![[NSUserDefaults standardUserDefaults] boolForKey:kOMSuppressDashNotInstalledWarning];
+ if (showNotInstalledWarning) {
+ NSAlert *alert = [NSAlert alertWithMessageText:@"Dash not installed" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"It looks like the Dash app is not installed on your system. Please visit http://kapeli.com/dash/ to get it."];
+ [alert setShowsSuppressionButton:YES];
+ [alert runModal];
+ if ([[alert suppressionButton] state] == NSOnState) {
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kOMSuppressDashNotInstalledWarning];
+ }
+ }
+}
+
+- (void)om_textView:(NSTextView *)textView didClickOnTemporaryLinkAtCharacterIndex:(unsigned long long)charIndex event:(NSEvent *)event isAltEvent:(BOOL)isAltEvent
+{
+ BOOL dashDisabled = [[NSUserDefaults standardUserDefaults] boolForKey:kOMOpenInDashDisabled];
+ if (isAltEvent && !dashDisabled) {
+ @try {
+ NSArray *linkRanges = [textView valueForKey:@"_temporaryLinkRanges"];
+ NSMutableString *searchString = [NSMutableString string];
+ for (NSValue *rangeValue in linkRanges) {
+ NSRange range = [rangeValue rangeValue];
+ NSString *stringFromRange = [textView.textStorage.string substringWithRange:range];
+ [searchString appendString:stringFromRange];
+ }
+ BOOL dashOpened = [self om_showQuickHelpForSearchString:searchString];
+ if (!dashOpened) {
+ [self om_dashNotInstalledFallback];
+ }
+ }
+ @catch (NSException *exception) {
+
+ }
+ } else {
+ //Preserve the default behavior for cmd-clicks:
+ [self om_textView:textView didClickOnTemporaryLinkAtCharacterIndex:charIndex event:event isAltEvent:isAltEvent];
+ }
+}
+
+- (BOOL)om_showQuickHelpForSearchString:(NSString *)searchString
+{
+ if (searchString.length == 0) {
+ NSBeep();
+ } else {
+ BOOL opened = [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"dash://%@", searchString]]];
+ if (!opened) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+@end
+
+
+
+@implementation OMQuickHelpPlugin
+
++ (void)pluginDidLoad:(NSBundle *)plugin
+{
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ if (NSClassFromString(@"IDESourceCodeEditor") != NULL) {
+ [NSClassFromString(@"IDESourceCodeEditor") jr_swizzleMethod:@selector(showQuickHelp:) withMethod:@selector(om_showQuickHelp:) error:NULL];
+ [NSClassFromString(@"IDESourceCodeEditor") jr_swizzleMethod:@selector(textView:didClickOnTemporaryLinkAtCharacterIndex:event:isAltEvent:) withMethod:@selector(om_textView:didClickOnTemporaryLinkAtCharacterIndex:event:isAltEvent:) error:NULL];
+ }
+ [[self alloc] init];
+ });
+}
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ //TODO: It would be better to add this to the Help menu, but that seems to be populated from somewhere else...
+ NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
+ if (editMenuItem) {
+ [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]];
+ NSMenuItem *toggleDashItem = [[[NSMenuItem alloc] initWithTitle:@"Open Quick Help in Dash" action:@selector(toggleOpenInDashEnabled:) keyEquivalent:@""] autorelease];
+ [toggleDashItem setTarget:self];
+ [[editMenuItem submenu] addItem:toggleDashItem];
+ }
+ }
+ return self;
+}
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
+{
+ if ([menuItem action] == @selector(toggleOpenInDashEnabled:)) {
+ if ([[NSUserDefaults standardUserDefaults] boolForKey:kOMOpenInDashDisabled]) {
+ [menuItem setState:NSOffState];
+ } else {
+ [menuItem setState:NSOnState];
+ }
+ }
+ return YES;
+}
+
+- (void)toggleOpenInDashEnabled:(id)sender
+{
+ BOOL disabled = [[NSUserDefaults standardUserDefaults] boolForKey:kOMOpenInDashDisabled];
+ [[NSUserDefaults standardUserDefaults] setBool:!disabled forKey:kOMOpenInDashDisabled];
+}
+
+@end
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.omz-software.${PRODUCT_NAME:identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${CURRENT_PROJECT_VERSION}</string>
+ <key>CFBundleVersion</key>
+ <string>${CURRENT_PROJECT_VERSION}</string>
+ <key>NSPrincipalClass</key>
+ <string>OMQuickHelpPlugin</string>
+ <key>XCGCReady</key>
+ <true/>
+ <key>XCPluginHasUI</key>
+ <false/>
+ <key>XC4Compatible</key>
+ <true/>
+</dict>
+</plist>
Oops, something went wrong.

0 comments on commit b798ec4

Please sign in to comment.