├── .gitignore ├── README.md ├── Reveal-In-GitHub.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── Reveal-In-GitHub ├── Info.plist ├── RIGConfig.h ├── RIGConfig.m ├── RIGConfigCellView.h ├── RIGConfigCellView.m ├── RIGConfigCellsView.h ├── RIGConfigCellsView.m ├── RIGGitRepo.h ├── RIGGitRepo.m ├── RIGPlugin.h ├── RIGPlugin.m ├── RIGSetting.h ├── RIGSetting.m ├── RIGSettingWindowController.h ├── RIGSettingWindowController.m ├── RIGSettingWindowController.xib ├── RIGUtils.h └── RIGUtils.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | # CocoaPods 30 | Pods/ 31 | Podfile.lock 32 | 33 | *.gcda 34 | *.gcno 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reveal-In-GitHub 2 | 3 | An Xcode plugin designed for seamless navigation to key GitHub functionalities within your current repository. With just a click, effortlessly access GitHub History, Blame, Pull Requests, Issues, and Notifications, all in a matter of seconds. 4 | 5 | ![plugin](https://cloud.githubusercontent.com/assets/5022872/10867703/96e980be-80ab-11e5-9aaa-a06ef476b7f7.gif) 6 | 7 | My company work on GitHub. I open the GitHub a lot. Sometimes, I am editing on Xcode and do not understand some code, so I go to GitHub to blame them. Sometimes, find the latest commits about a file to help me figure out how the code evolves. So I wonder is there a tool to help me quick open GitHub from Xcode. So I write this plugin. When you editing some source file on Xcode, it's easy to know which GitHub repo you are working on and know which file you are editing. So it makes sense to quickly jump to the file on GitHub, quickly jump to blame the current editing line on GitHub, quickly jump to the issues or prs of the current repo you are working on Xcode. 8 | 9 | ## Menu Items 10 | 11 | 2015-11-01 12 56 35 12 | 13 | It has six menu items: 14 | 15 | Menu Title | Shortcut | GitHub URL Pattern (When I'm editing LZAlbumManager.m Line 40) 16 | ----------------|-----------------------|---------------------------------- 17 | Setting |⌃⇧⌘S | 18 | Repo |⌃⇧⌘R | https://github.com/lzwjava/LZAlbum 19 | Issues |⌃⇧⌘I | https://github.com/lzwjava/LZAlbum/issues 20 | PRs |⌃⇧⌘P | https://github.com/lzwjava/LZAlbum/pulls 21 | Quick File |⌃⇧⌘Q | https://github.com/lzwjava/LZAlbum/blob/fd7224/LZAlbum/manager/LZAlbumManager.m#L40 22 | List History |⌃⇧⌘L | https://github.com/lzwjava/LZAlbum/commits/fd7224/LZAlbum/manager/LZAlbumManager.m 23 | Blame |⌃⇧⌘B | https://github.com/lzwjava/LZAlbum/blame/fd7224/LZAlbum/manager/LZAlbumManager.m#L40 24 | Notifications |⌃⇧⌘N | https://github.com/leancloud/LZAlbum/notifications?all=1 25 | 26 | The shortcuts are carefully designed. They will not confict to Xcode default shortcuts. The shortcut pattern is ⌃⇧⌘ (Ctrl+Shift+Command), plus First Character of the menu title. 27 | 28 | ## Customize 29 | 30 | Sometimes, you may want to quickly jump to Wiki. Here is the way, open the setting: 31 | 32 | 2015-11-01 12 56 35 33 | 34 | For example, 35 | 36 | Quick file, the pattern and the actual url: 37 | 38 | ``` 39 | {git_remote_url} /blob/{commit}/ {file_path} #{selection} 40 | https://github.com/lzwjava/LZAlbum/blob/fd7224/LZAlbum/manager/LZAlbumManager.m#L40-L43 41 | ``` 42 | 43 | The {commit} is the latest commit hash of current branch. It's better then use branch. Because branch's HEAD may be changed. So the code in #L40-L43 may also be changed. 44 | 45 | So if you want to add a shortcut to current repo's wiki, just add a menu item and set the pattern to ` {git_remote_url}/wiki`. 46 | 47 | In settings, `Clear Default Repos` say if you have multiple git remotes, when first time to trigger, it will ask you to choose one of them: 48 | 49 | 50 | 51 | Then the plugin remembers which you choose. So when you trigger the menu again, will open that remote repo as the default. The button `Clear Default Repos` will clear this setting, will ask you to select again. 52 | 53 | ## Install 54 | 55 | Recomend install with [Alcatraz](http://alcatraz.io/), 56 | 57 | ![qq20151101-1 2x](https://cloud.githubusercontent.com/assets/5022872/10867743/0ce351c6-80ae-11e5-82e2-f740887153f7.jpg) 58 | 59 | Or 60 | 61 | 1. Clone this repo. 62 | 2. Open `Reveal-In-GitHub.xcodeproj`, and build it. 63 | 3. Reveal-In-GitHub.xcplugin should locate at `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins` 64 | 4. Restart Xcode 65 | 5. Open any GitHub Project and press ⌃⇧⌘B (Ctrl+Shift+Command+B) to blame the code. 66 | 67 | ## 安装 68 | 69 | 推荐使用 [Alcatraz](http://alcatraz.io/),可参考巧神的一遍[博客](http://blog.devtang.com/blog/2014/03/05/use-alcatraz-to-manage-xcode-plugins/)。安装好后,按照上图搜索 `Reveal In GitHub`,点击 `Install` 即可。 70 | 71 | 如果不使用这个工具的话,那么也只需三步: 72 | 73 | * 克隆该项目到本地。 74 | * 打开 xcodeproj,点击 Build 构建。这会在目录`~/Library/Application Support/Developer/Shared/Xcode/Plug-ins` 生成 Reveal-In-GitHub.xcplugin 文件。 75 | * 重启 Xcode,打开任意一个放在 GitHub 上的项目。按下 `Ctrl+Shift+Command+B` 即可。 76 | 77 | ## Credit 78 | 79 | When at the course of developing it, find another plugin [ShowInGitHub](https://github.com/larsxschneider/ShowInGitHub) do something similar. I learn some techique from it. Thanks for that. 80 | 81 | ## License 82 | 83 | MIT 84 | -------------------------------------------------------------------------------- /Reveal-In-GitHub.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 706F254E1BE7C76E00CA15B4 /* RIGConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F253D1BE7C76E00CA15B4 /* RIGConfig.m */; }; 11 | 706F254F1BE7C76E00CA15B4 /* RIGConfigCellsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F253F1BE7C76E00CA15B4 /* RIGConfigCellsView.m */; }; 12 | 706F25501BE7C76E00CA15B4 /* RIGConfigCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F25411BE7C76E00CA15B4 /* RIGConfigCellView.m */; }; 13 | 706F25511BE7C76E00CA15B4 /* RIGGitRepo.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F25431BE7C76E00CA15B4 /* RIGGitRepo.m */; }; 14 | 706F25521BE7C76E00CA15B4 /* RIGPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F25451BE7C76E00CA15B4 /* RIGPlugin.m */; }; 15 | 706F25531BE7C76E00CA15B4 /* RIGSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F25471BE7C76E00CA15B4 /* RIGSetting.m */; }; 16 | 706F25541BE7C76E00CA15B4 /* RIGSettingWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F25491BE7C76E00CA15B4 /* RIGSettingWindowController.m */; }; 17 | 706F25551BE7C76E00CA15B4 /* RIGSettingWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 706F254A1BE7C76E00CA15B4 /* RIGSettingWindowController.xib */; }; 18 | 706F25561BE7C76E00CA15B4 /* RIGUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 706F254C1BE7C76E00CA15B4 /* RIGUtils.m */; }; 19 | 70DE90B11BD932D300E29A15 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70DE90B01BD932D300E29A15 /* Cocoa.framework */; }; 20 | 70DE90B31BD932D800E29A15 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70DE90B21BD932D800E29A15 /* Foundation.framework */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 705EEF501BD80FB900A29083 /* Reveal-In-GitHub.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Reveal-In-GitHub.xcplugin"; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 706F253B1BE7C76E00CA15B4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 706F253C1BE7C76E00CA15B4 /* RIGConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGConfig.h; sourceTree = ""; }; 27 | 706F253D1BE7C76E00CA15B4 /* RIGConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGConfig.m; sourceTree = ""; }; 28 | 706F253E1BE7C76E00CA15B4 /* RIGConfigCellsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGConfigCellsView.h; sourceTree = ""; }; 29 | 706F253F1BE7C76E00CA15B4 /* RIGConfigCellsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGConfigCellsView.m; sourceTree = ""; }; 30 | 706F25401BE7C76E00CA15B4 /* RIGConfigCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGConfigCellView.h; sourceTree = ""; }; 31 | 706F25411BE7C76E00CA15B4 /* RIGConfigCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGConfigCellView.m; sourceTree = ""; }; 32 | 706F25421BE7C76E00CA15B4 /* RIGGitRepo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGGitRepo.h; sourceTree = ""; }; 33 | 706F25431BE7C76E00CA15B4 /* RIGGitRepo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGGitRepo.m; sourceTree = ""; }; 34 | 706F25441BE7C76E00CA15B4 /* RIGPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGPlugin.h; sourceTree = ""; }; 35 | 706F25451BE7C76E00CA15B4 /* RIGPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGPlugin.m; sourceTree = ""; }; 36 | 706F25461BE7C76E00CA15B4 /* RIGSetting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGSetting.h; sourceTree = ""; }; 37 | 706F25471BE7C76E00CA15B4 /* RIGSetting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGSetting.m; sourceTree = ""; }; 38 | 706F25481BE7C76E00CA15B4 /* RIGSettingWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGSettingWindowController.h; sourceTree = ""; }; 39 | 706F25491BE7C76E00CA15B4 /* RIGSettingWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGSettingWindowController.m; sourceTree = ""; }; 40 | 706F254A1BE7C76E00CA15B4 /* RIGSettingWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RIGSettingWindowController.xib; sourceTree = ""; }; 41 | 706F254B1BE7C76E00CA15B4 /* RIGUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RIGUtils.h; sourceTree = ""; }; 42 | 706F254C1BE7C76E00CA15B4 /* RIGUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RIGUtils.m; sourceTree = ""; }; 43 | 70DE90B01BD932D300E29A15 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 44 | 70DE90B21BD932D800E29A15 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 705EEF4D1BD80FB900A29083 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 70DE90B31BD932D800E29A15 /* Foundation.framework in Frameworks */, 53 | 70DE90B11BD932D300E29A15 /* Cocoa.framework in Frameworks */, 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | 705EEF471BD80FB900A29083 = { 61 | isa = PBXGroup; 62 | children = ( 63 | 70DE90B21BD932D800E29A15 /* Foundation.framework */, 64 | 70DE90B01BD932D300E29A15 /* Cocoa.framework */, 65 | 706F25391BE7C76E00CA15B4 /* Reveal-In-GitHub */, 66 | 705EEF511BD80FB900A29083 /* Products */, 67 | ); 68 | sourceTree = ""; 69 | }; 70 | 705EEF511BD80FB900A29083 /* Products */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 705EEF501BD80FB900A29083 /* Reveal-In-GitHub.xcplugin */, 74 | ); 75 | name = Products; 76 | sourceTree = ""; 77 | }; 78 | 706F25391BE7C76E00CA15B4 /* Reveal-In-GitHub */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 706F253B1BE7C76E00CA15B4 /* Info.plist */, 82 | 706F253C1BE7C76E00CA15B4 /* RIGConfig.h */, 83 | 706F253D1BE7C76E00CA15B4 /* RIGConfig.m */, 84 | 706F253E1BE7C76E00CA15B4 /* RIGConfigCellsView.h */, 85 | 706F253F1BE7C76E00CA15B4 /* RIGConfigCellsView.m */, 86 | 706F25401BE7C76E00CA15B4 /* RIGConfigCellView.h */, 87 | 706F25411BE7C76E00CA15B4 /* RIGConfigCellView.m */, 88 | 706F25421BE7C76E00CA15B4 /* RIGGitRepo.h */, 89 | 706F25431BE7C76E00CA15B4 /* RIGGitRepo.m */, 90 | 706F25441BE7C76E00CA15B4 /* RIGPlugin.h */, 91 | 706F25451BE7C76E00CA15B4 /* RIGPlugin.m */, 92 | 706F25461BE7C76E00CA15B4 /* RIGSetting.h */, 93 | 706F25471BE7C76E00CA15B4 /* RIGSetting.m */, 94 | 706F25481BE7C76E00CA15B4 /* RIGSettingWindowController.h */, 95 | 706F25491BE7C76E00CA15B4 /* RIGSettingWindowController.m */, 96 | 706F254A1BE7C76E00CA15B4 /* RIGSettingWindowController.xib */, 97 | 706F254B1BE7C76E00CA15B4 /* RIGUtils.h */, 98 | 706F254C1BE7C76E00CA15B4 /* RIGUtils.m */, 99 | ); 100 | path = "Reveal-In-GitHub"; 101 | sourceTree = ""; 102 | }; 103 | /* End PBXGroup section */ 104 | 105 | /* Begin PBXNativeTarget section */ 106 | 705EEF4F1BD80FB900A29083 /* Reveal-In-GitHub */ = { 107 | isa = PBXNativeTarget; 108 | buildConfigurationList = 705EEF561BD80FB900A29083 /* Build configuration list for PBXNativeTarget "Reveal-In-GitHub" */; 109 | buildPhases = ( 110 | 705EEF4C1BD80FB900A29083 /* Sources */, 111 | 705EEF4D1BD80FB900A29083 /* Frameworks */, 112 | 705EEF4E1BD80FB900A29083 /* Resources */, 113 | ); 114 | buildRules = ( 115 | ); 116 | dependencies = ( 117 | ); 118 | name = "Reveal-In-GitHub"; 119 | productName = "Reveal-In-Github"; 120 | productReference = 705EEF501BD80FB900A29083 /* Reveal-In-GitHub.xcplugin */; 121 | productType = "com.apple.product-type.bundle"; 122 | }; 123 | /* End PBXNativeTarget section */ 124 | 125 | /* Begin PBXProject section */ 126 | 705EEF481BD80FB900A29083 /* Project object */ = { 127 | isa = PBXProject; 128 | attributes = { 129 | LastUpgradeCheck = 0700; 130 | ORGANIZATIONNAME = lzwjava; 131 | TargetAttributes = { 132 | 705EEF4F1BD80FB900A29083 = { 133 | CreatedOnToolsVersion = 7.0; 134 | }; 135 | }; 136 | }; 137 | buildConfigurationList = 705EEF4B1BD80FB900A29083 /* Build configuration list for PBXProject "Reveal-In-GitHub" */; 138 | compatibilityVersion = "Xcode 3.2"; 139 | developmentRegion = English; 140 | hasScannedForEncodings = 0; 141 | knownRegions = ( 142 | en, 143 | ); 144 | mainGroup = 705EEF471BD80FB900A29083; 145 | productRefGroup = 705EEF511BD80FB900A29083 /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | 705EEF4F1BD80FB900A29083 /* Reveal-In-GitHub */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXResourcesBuildPhase section */ 155 | 705EEF4E1BD80FB900A29083 /* Resources */ = { 156 | isa = PBXResourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | 706F25551BE7C76E00CA15B4 /* RIGSettingWindowController.xib in Resources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | 705EEF4C1BD80FB900A29083 /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 706F25521BE7C76E00CA15B4 /* RIGPlugin.m in Sources */, 171 | 706F25541BE7C76E00CA15B4 /* RIGSettingWindowController.m in Sources */, 172 | 706F25531BE7C76E00CA15B4 /* RIGSetting.m in Sources */, 173 | 706F254F1BE7C76E00CA15B4 /* RIGConfigCellsView.m in Sources */, 174 | 706F25561BE7C76E00CA15B4 /* RIGUtils.m in Sources */, 175 | 706F25501BE7C76E00CA15B4 /* RIGConfigCellView.m in Sources */, 176 | 706F254E1BE7C76E00CA15B4 /* RIGConfig.m in Sources */, 177 | 706F25511BE7C76E00CA15B4 /* RIGGitRepo.m in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin XCBuildConfiguration section */ 184 | 705EEF541BD80FB900A29083 /* Debug */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | ALWAYS_SEARCH_USER_PATHS = NO; 188 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 189 | CLANG_CXX_LIBRARY = "libc++"; 190 | CLANG_ENABLE_MODULES = YES; 191 | CLANG_ENABLE_OBJC_ARC = YES; 192 | CLANG_WARN_BOOL_CONVERSION = YES; 193 | CLANG_WARN_CONSTANT_CONVERSION = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_EMPTY_BODY = YES; 196 | CLANG_WARN_ENUM_CONVERSION = YES; 197 | CLANG_WARN_INT_CONVERSION = YES; 198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = NO; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_STRICT_OBJC_MSGSEND = YES; 204 | ENABLE_TESTABILITY = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu99; 206 | GCC_DYNAMIC_NO_PIC = NO; 207 | GCC_NO_COMMON_BLOCKS = YES; 208 | GCC_OPTIMIZATION_LEVEL = 0; 209 | GCC_PREPROCESSOR_DEFINITIONS = ( 210 | "DEBUG=1", 211 | "$(inherited)", 212 | ); 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 215 | GCC_WARN_UNDECLARED_SELECTOR = YES; 216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 217 | GCC_WARN_UNUSED_FUNCTION = YES; 218 | GCC_WARN_UNUSED_VARIABLE = YES; 219 | MACOSX_DEPLOYMENT_TARGET = 10.10; 220 | MTL_ENABLE_DEBUG_INFO = YES; 221 | ONLY_ACTIVE_ARCH = YES; 222 | SDKROOT = macosx; 223 | }; 224 | name = Debug; 225 | }; 226 | 705EEF551BD80FB900A29083 /* Release */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 231 | CLANG_CXX_LIBRARY = "libc++"; 232 | CLANG_ENABLE_MODULES = YES; 233 | CLANG_ENABLE_OBJC_ARC = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INT_CONVERSION = YES; 240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 241 | CLANG_WARN_UNREACHABLE_CODE = YES; 242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 243 | COPY_PHASE_STRIP = NO; 244 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 245 | ENABLE_NS_ASSERTIONS = NO; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | GCC_C_LANGUAGE_STANDARD = gnu99; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | MACOSX_DEPLOYMENT_TARGET = 10.10; 256 | MTL_ENABLE_DEBUG_INFO = NO; 257 | SDKROOT = macosx; 258 | }; 259 | name = Release; 260 | }; 261 | 705EEF571BD80FB900A29083 /* Debug */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | COMBINE_HIDPI_IMAGES = YES; 265 | DEPLOYMENT_LOCATION = YES; 266 | DSTROOT = ""; 267 | GCC_PREPROCESSOR_DEFINITIONS = ( 268 | "DEBUG=1", 269 | "$(inherited)", 270 | ); 271 | HEADER_SEARCH_PATHS = ""; 272 | INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; 273 | INSTALL_PATH = "~/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 274 | PRODUCT_BUNDLE_IDENTIFIER = "com.lzwjava.Reveal-In-GitHub"; 275 | PRODUCT_NAME = "$(TARGET_NAME)"; 276 | SKIP_INSTALL = NO; 277 | WRAPPER_EXTENSION = xcplugin; 278 | }; 279 | name = Debug; 280 | }; 281 | 705EEF581BD80FB900A29083 /* Release */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | COMBINE_HIDPI_IMAGES = YES; 285 | DEPLOYMENT_LOCATION = YES; 286 | DSTROOT = ""; 287 | HEADER_SEARCH_PATHS = ""; 288 | INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; 289 | INSTALL_PATH = "~/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 290 | PRODUCT_BUNDLE_IDENTIFIER = "com.lzwjava.Reveal-In-GitHub"; 291 | PRODUCT_NAME = "$(TARGET_NAME)"; 292 | SKIP_INSTALL = NO; 293 | WRAPPER_EXTENSION = xcplugin; 294 | }; 295 | name = Release; 296 | }; 297 | /* End XCBuildConfiguration section */ 298 | 299 | /* Begin XCConfigurationList section */ 300 | 705EEF4B1BD80FB900A29083 /* Build configuration list for PBXProject "Reveal-In-GitHub" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | 705EEF541BD80FB900A29083 /* Debug */, 304 | 705EEF551BD80FB900A29083 /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | 705EEF561BD80FB900A29083 /* Build configuration list for PBXNativeTarget "Reveal-In-GitHub" */ = { 310 | isa = XCConfigurationList; 311 | buildConfigurations = ( 312 | 705EEF571BD80FB900A29083 /* Debug */, 313 | 705EEF581BD80FB900A29083 /* Release */, 314 | ); 315 | defaultConfigurationIsVisible = 0; 316 | defaultConfigurationName = Release; 317 | }; 318 | /* End XCConfigurationList section */ 319 | }; 320 | rootObject = 705EEF481BD80FB900A29083 /* Project object */; 321 | } 322 | -------------------------------------------------------------------------------- /Reveal-In-GitHub.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | DVTPlugInCompatibilityUUIDs 24 | 25 | 63FC1C47-140D-42B0-BB4D-A10B2D225574 26 | 37B30044-3B14-46BA-ABAA-F01000C27B63 27 | 640F884E-CE55-4B40-87C0-8869546CAB7A 28 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 29 | AD68E85B-441B-4301-B564-A45E4919A6AD 30 | C4A681B0-4A26-480E-93EC-1218098B9AA0 31 | FEC992CC-CA4A-4CFD-8881-77300FCB848A 32 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 33 | A16FF353-8441-459E-A50C-B071F53F51B7 34 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 35 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 36 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 37 | E969541F-E6F9-4D25-8158-72DC3545A6C6 38 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 39 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 40 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 41 | CC0D0F4F-05B3-431A-8F33-F84AFCB2C651 42 | 7265231C-39B4-402C-89E1-16167C4CC990 43 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 44 | F41BD31E-2683-44B8-AE7F-5F09E919790E 45 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 46 | 47 | NSHumanReadableCopyright 48 | Copyright © 2015年 lzwjava. All rights reserved. 49 | NSPrincipalClass 50 | 51 | XC4Compatible 52 | 53 | XC5Compatible 54 | 55 | XCGCReady 56 | 57 | XCPluginHasUI 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // RIGConfig.h 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/28. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RIGConfig : NSObject 12 | 13 | @property (nonatomic, strong) NSString *menuTitle; 14 | @property (nonatomic, strong) NSString *lastKey; 15 | @property (nonatomic, strong) NSString *pattern; 16 | 17 | - (instancetype)initWithDictionary:(NSDictionary *)dict; 18 | - (NSDictionary *)dictionary; 19 | 20 | + (instancetype)configWithMenuTitle:(NSString *)menuTitle lastKey:(NSString *)lastKey pattern:(NSString *)pattern; 21 | 22 | - (BOOL)isValid; 23 | 24 | - (void)removeNil; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGConfig.m 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/28. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import "RIGConfig.h" 10 | 11 | #define SEL_TO_STRING(sel) NSStringFromSelector(@selector(sel)) 12 | 13 | @implementation RIGConfig 14 | 15 | - (instancetype)initWithDictionary:(NSDictionary *)dict { 16 | self = [super init]; 17 | if (self) { 18 | _menuTitle = dict[SEL_TO_STRING(menuTitle)]; 19 | _lastKey = dict[SEL_TO_STRING(lastKey)]; 20 | _pattern = dict[SEL_TO_STRING(pattern)]; 21 | } 22 | return self; 23 | } 24 | 25 | - (NSDictionary *)dictionary { 26 | NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; 27 | if (self.menuTitle.length > 0) { 28 | [dict setObject:self.menuTitle forKey:SEL_TO_STRING(menuTitle)]; 29 | } 30 | if (self.lastKey.length > 0) { 31 | [dict setObject:self.lastKey forKey:SEL_TO_STRING(lastKey)]; 32 | } 33 | if (self.pattern.length > 0) { 34 | [dict setObject:self.pattern forKey:SEL_TO_STRING(pattern)]; 35 | } 36 | return dict; 37 | } 38 | 39 | + (instancetype)configWithMenuTitle:(NSString *)menuTitle lastKey:(NSString *)lastKey pattern:(NSString *)pattern { 40 | RIGConfig *config = [[self alloc] init]; 41 | config.menuTitle = menuTitle; 42 | config.lastKey = lastKey; 43 | config.pattern = pattern; 44 | return config; 45 | } 46 | 47 | - (BOOL)isValid { 48 | return self.menuTitle.length > 0 && self.pattern.length > 0; 49 | } 50 | 51 | - (NSString *)description { 52 | return [NSString stringWithFormat:@"RIGConfig 10 | #import "RIGConfig.h" 11 | 12 | @interface RIGConfigCellView : NSView 13 | 14 | @property (nonatomic, strong) NSTextField *menuTitleField; 15 | @property (nonatomic, strong) NSTextField *lastKeyField; 16 | @property (nonatomic, strong) NSTextField *patternField; 17 | 18 | @property (nonatomic, strong) RIGConfig *config; 19 | 20 | + (CGFloat)heightForCellView; 21 | 22 | - (void)reloadData; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGConfigCellView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGConfigCellView.m 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/28. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import "RIGConfigCellView.h" 10 | 11 | #define kHorizontalMargin 8 12 | #define kVerticalMargin 2 13 | #define kTextFieldHeight 25 14 | #define kMenuTitleWidth 100 15 | #define kLastKeyWidth 30 16 | 17 | @interface RIGConfigCellView() 18 | 19 | @end 20 | 21 | @implementation RIGConfigCellView 22 | 23 | - (instancetype)initWithFrame:(NSRect)frame 24 | { 25 | self = [super initWithFrame:frame]; 26 | if (self) { 27 | // self.wantsLayer = YES; 28 | // self.layer.backgroundColor = [NSColor greenColor].CGColor; 29 | [self addSubview:self.menuTitleField]; 30 | [self addSubview:self.lastKeyField]; 31 | [self addSubview:self.patternField]; 32 | } 33 | return self; 34 | } 35 | 36 | + (CGFloat)heightForCellView { 37 | return kVerticalMargin * 2 + kTextFieldHeight; 38 | } 39 | 40 | - (void)commonInitTextField:(NSTextField *)textField { 41 | textField.cell.wraps = NO; 42 | textField.cell.scrollable = YES; 43 | textField.delegate = self; 44 | // textField.font = [NSFont systemFontOfSize:16]; 45 | } 46 | 47 | - (NSTextField *)menuTitleField { 48 | if (_menuTitleField == nil) { 49 | _menuTitleField = [[NSTextField alloc] initWithFrame:CGRectMake(kHorizontalMargin, kVerticalMargin, kMenuTitleWidth, kTextFieldHeight)]; 50 | [self commonInitTextField:_menuTitleField]; 51 | } 52 | return _menuTitleField; 53 | } 54 | 55 | - (NSTextField *)lastKeyField { 56 | if (_lastKeyField == nil) { 57 | _lastKeyField = [[NSTextField alloc] initWithFrame:CGRectMake(CGRectGetMaxX(self.menuTitleField.frame) + kHorizontalMargin, kVerticalMargin , kLastKeyWidth, kTextFieldHeight)]; 58 | [self commonInitTextField:_lastKeyField]; 59 | } 60 | return _lastKeyField; 61 | } 62 | 63 | - (NSTextField *)patternField { 64 | if (_patternField == nil) { 65 | CGFloat maxX = CGRectGetMaxX(self.lastKeyField.frame); 66 | _patternField = [[NSTextField alloc] initWithFrame:CGRectMake(maxX + kHorizontalMargin, kVerticalMargin , CGRectGetWidth(self.frame) - maxX - 2 * kHorizontalMargin, kTextFieldHeight)]; 67 | [self commonInitTextField:_patternField]; 68 | } 69 | return _patternField; 70 | } 71 | 72 | - (void)reloadData { 73 | [self.config removeNil]; 74 | self.menuTitleField.stringValue = self.config.menuTitle; 75 | self.lastKeyField.stringValue = self.config.lastKey; 76 | self.patternField.stringValue = self.config.pattern; 77 | if (self.config) { 78 | NSDictionary* textFieldBindingOptions = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], NSValidatesImmediatelyBindingOption, [NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption, 79 | nil]; 80 | [self.menuTitleField bind:@"value" toObject:self.config withKeyPath:@"menuTitle" options:textFieldBindingOptions]; 81 | [self.lastKeyField bind:@"value" toObject:self.config withKeyPath:@"lastKey" options:textFieldBindingOptions]; 82 | [self.patternField bind:@"value" toObject:self.config withKeyPath:@"pattern" options:textFieldBindingOptions]; 83 | } 84 | } 85 | 86 | - (void)controlTextDidChange:(NSNotification *)notification { 87 | NSLog(@"Did change"); 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGConfigCellsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RIGConfigCellViews.h 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/30. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RIGConfigCellsView : NSView 12 | 13 | @property (nonatomic, strong) NSArray *configs; 14 | 15 | + (CGFloat)heightForConfigs:(NSArray *)configs; 16 | 17 | - (void)reloadData; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGConfigCellsView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGConfigCellViews.m 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/30. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import "RIGConfigCellsView.h" 10 | #import "RIGConfigCellView.h" 11 | 12 | #define kVerticalMargin 5 13 | 14 | @interface RIGConfigCellsView() 15 | 16 | @property (nonatomic, strong) NSMutableArray *cellViews; 17 | 18 | @end 19 | 20 | @implementation RIGConfigCellsView 21 | 22 | - (instancetype)initWithFrame:(NSRect)frame 23 | { 24 | self = [super initWithFrame:frame]; 25 | if (self) { 26 | _cellViews = [NSMutableArray array]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)drawRect:(NSRect)dirtyRect { 32 | [super drawRect:dirtyRect]; 33 | 34 | // Drawing code here. 35 | } 36 | 37 | + (CGFloat)heightForConfigs:(NSArray *)configs { 38 | return configs.count * [RIGConfigCellView heightForCellView] + (configs.count - 1) * kVerticalMargin; 39 | } 40 | 41 | - (void)reloadData { 42 | CGFloat w = CGRectGetWidth(self.frame); 43 | for (NSView *view in self.cellViews) { 44 | [view removeFromSuperview]; 45 | } 46 | for (RIGConfig *config in self.configs) { 47 | NSInteger index = [self.configs indexOfObject:config]; 48 | CGFloat subH = [RIGConfigCellView heightForCellView]; 49 | CGFloat y = (self.configs.count - 1 - index) * (subH + kVerticalMargin); 50 | RIGConfigCellView *cellView = [[RIGConfigCellView alloc] initWithFrame:NSRectFromCGRect(CGRectMake(0, y, w, subH))]; 51 | cellView.config = config; 52 | [cellView reloadData]; 53 | [self.cellViews addObject:cellView]; 54 | [self addSubview:cellView]; 55 | } 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGGitRepo.h: -------------------------------------------------------------------------------- 1 | // 2 | // RIGGitHelper.h 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/31. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RIGGitRepo : NSObject 12 | 13 | @property (nonatomic, copy, readonly) NSString *localPath; 14 | 15 | - (instancetype)initWithDocumentURL:(NSURL *)documentURL; 16 | 17 | - (BOOL)isValid; 18 | 19 | - (NSString *)latestCommitHash; 20 | - (NSString *)filenameWithPathInCommit:(NSString *)commitHash; 21 | 22 | - (NSString *)remoteRepoUrl; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGGitRepo.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGGitHelper.m 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/31. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import "RIGGitRepo.h" 10 | #import "RIGUtils.h" 11 | #import "RIGSetting.h" 12 | 13 | @interface RIGGitRepo() 14 | 15 | @property (nonatomic, strong) NSURL *documentURL; 16 | @property (nonatomic, strong) NSString *documentDirectoryPath; 17 | @property (nonatomic, copy) NSString *localPath; 18 | 19 | @end 20 | 21 | @implementation RIGGitRepo 22 | 23 | - (instancetype)initWithDocumentURL:(NSURL *)documentURL { 24 | if (self == [super init]) { 25 | _documentURL = documentURL; 26 | _documentDirectoryPath = [[documentURL URLByDeletingLastPathComponent] path]; 27 | _localPath = [self gitLocalPath]; 28 | } 29 | return self; 30 | } 31 | 32 | - (NSString *)filenameWithPathInCommit:(NSString *)commitHash { 33 | NSArray *args = @[@"show", @"--name-only", @"--pretty=format:", commitHash]; 34 | NSString *files = [self outputGitWithArguments:args inPath:self.documentDirectoryPath]; 35 | 36 | NSString *documentFilename = [self.documentURL lastPathComponent]; 37 | NSString *filenameWithPathInCommit = nil; 38 | for (NSString *filenameWithPath in [files componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) 39 | { 40 | if ([filenameWithPath hasSuffix:documentFilename]) 41 | { 42 | filenameWithPathInCommit = [filenameWithPath stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; 43 | break; 44 | } 45 | } 46 | 47 | if (!filenameWithPathInCommit) 48 | { 49 | LZLog(@"Unable to find file in commit."); 50 | return nil; 51 | } 52 | 53 | return filenameWithPathInCommit; 54 | } 55 | 56 | - (NSString *)latestCommitHash { 57 | NSString *documentFullPath = [self.documentURL path]; 58 | // Get last commit hash 59 | NSArray *args = @[@"log", @"-n1", @"--no-decorate", documentFullPath]; 60 | NSString *rawLastCommitHash = [self outputGitWithArguments:args inPath:self.documentDirectoryPath]; 61 | LZLog(@"GIT log: %@", rawLastCommitHash); 62 | NSArray *commitHashInfo = [rawLastCommitHash componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 63 | 64 | if (commitHashInfo.count < 2) 65 | { 66 | // [RIGUtils showMessage:@"Unable to find lastest commit."]; 67 | return nil; 68 | } 69 | 70 | NSString *commitHash = [commitHashInfo objectAtIndex:1]; 71 | return commitHash; 72 | } 73 | 74 | - (NSString *)gitLocalPath { 75 | NSURL *activeDocumentURL = self.documentURL; 76 | NSString *activeDocumentDirectoryPath = [[activeDocumentURL URLByDeletingLastPathComponent] path]; 77 | NSArray *args = @[@"rev-parse", @"--show-toplevel"]; 78 | NSString *rootPath = [self outputGitWithArguments:args inPath:activeDocumentDirectoryPath]; 79 | return [rootPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 80 | } 81 | 82 | // Performs a git command with given args in the given directory 83 | - (NSString *)outputGitWithArguments:(NSArray *)args inPath:(NSString *)path 84 | { 85 | if (path.length == 0) 86 | { 87 | LZLog(@"Invalid path for git working directory."); 88 | return nil; 89 | } 90 | 91 | NSTask *task = [[NSTask alloc] init]; 92 | task.launchPath = @"/usr/bin/xcrun"; 93 | task.currentDirectoryPath = path; 94 | task.arguments = [@[@"git", @"--no-pager"] arrayByAddingObjectsFromArray:args]; 95 | task.standardOutput = [NSPipe pipe]; 96 | NSFileHandle *file = [task.standardOutput fileHandleForReading]; 97 | 98 | [task launch]; 99 | 100 | // For some reason [task waitUntilExit]; does not return sometimes. Therefore this rather hackish solution: 101 | int count = 0; 102 | while (task.isRunning && (count < 10)) 103 | { 104 | [NSThread sleepForTimeInterval:0.1]; 105 | count++; 106 | } 107 | 108 | NSString *output = [[NSString alloc] initWithData:[file readDataToEndOfFile] encoding:NSUTF8StringEncoding]; 109 | 110 | return output; 111 | } 112 | 113 | - (NSString *)remotePathFromRemoteURL:(NSString *)remotePath { 114 | // Check for SSH protocol 115 | NSRange begin = [remotePath rangeOfString:@"git@"]; 116 | 117 | if (begin.location == NSNotFound) 118 | { 119 | // SSH protocol not found, check for GIT protocol 120 | begin = [remotePath rangeOfString:@"git://"]; 121 | } 122 | if (begin.location == NSNotFound) 123 | { 124 | // HTTPS protocol check 125 | begin = [remotePath rangeOfString:@"https://"]; 126 | } 127 | if (begin.location == NSNotFound) 128 | { 129 | // HTTP protocol check 130 | begin = [remotePath rangeOfString:@"http://"]; 131 | } 132 | 133 | NSRange end = [remotePath rangeOfString:@".git (fetch)"]; 134 | 135 | if (end.location == NSNotFound) 136 | { 137 | // Alternate remote url end 138 | end = [remotePath rangeOfString:@" (fetch)"]; 139 | } 140 | 141 | if ((begin.location != NSNotFound) && 142 | (end.location != NSNotFound)) 143 | { 144 | NSUInteger githubURLBegin = begin.location + begin.length; 145 | NSUInteger githubURLLength = end.location - githubURLBegin; 146 | return [[remotePath 147 | substringWithRange:NSMakeRange(githubURLBegin, githubURLLength)] 148 | stringByReplacingOccurrencesOfString:@":" withString:@"/"]; 149 | } else { 150 | return nil; 151 | } 152 | } 153 | 154 | #pragma mark - Remote Repo 155 | 156 | - (NSString *)remoteRepoUrl { 157 | RIGSetting *setting = [RIGSetting settingForGitPath:self.localPath]; 158 | NSString *defaultRepo = [setting defaultRepo]; 159 | if (defaultRepo != nil) { 160 | return defaultRepo; 161 | } else { 162 | NSString *selectedRepo = [self getOrAskRemoteRepoUrl]; 163 | [setting setDefaultRepo:selectedRepo]; 164 | return selectedRepo; 165 | } 166 | } 167 | 168 | - (NSString *)getOrAskRemoteRepoUrl { 169 | NSString *rootPath = [self localPath]; 170 | // Get GitHub username and repo name 171 | NSArray *args = @[@"remote", @"--verbose"]; 172 | NSString *output = [self outputGitWithArguments:args inPath:rootPath]; 173 | NSArray *remoteURLs = [output componentsSeparatedByString:@"\n"]; 174 | 175 | NSMutableSet *remotePaths = [NSMutableSet set]; 176 | 177 | for (NSString *remoteURL in remoteURLs) 178 | { 179 | NSString *remotePath = [self remotePathFromRemoteURL:remoteURL]; 180 | if (remotePath) { 181 | [remotePaths addObject:remotePath]; 182 | } 183 | } 184 | 185 | NSString *selectedRemotePath; 186 | if (remotePaths.count == 1) { 187 | selectedRemotePath = [remotePaths allObjects][0]; 188 | } else if (remotePaths.count > 1) { 189 | NSArray *sortedRemotePaths = remotePaths.allObjects; 190 | 191 | // Ask the user what remote to use. 192 | // Attention: Due to NSRunAlert maximal three remotes are supported. 193 | 194 | NSAlert *alert = [[NSAlert alloc] init]; 195 | alert.alertStyle = NSInformationalAlertStyle; 196 | alert.messageText = [NSString stringWithFormat:@"This repository has %ld remotes configured. Which one do you want to open?", remotePaths.count]; 197 | [alert addButtonWithTitle:[sortedRemotePaths objectAtIndex:0]]; 198 | [alert addButtonWithTitle:[sortedRemotePaths objectAtIndex:1]]; 199 | [alert addButtonWithTitle:(sortedRemotePaths.count > 2 ? [sortedRemotePaths objectAtIndex:2] : nil)]; 200 | 201 | NSModalResponse button = [alert runModal]; 202 | if (button == NSAlertFirstButtonReturn) { 203 | selectedRemotePath = sortedRemotePaths[0]; 204 | } else if (button == NSAlertSecondButtonReturn) { 205 | selectedRemotePath = sortedRemotePaths[1]; 206 | } else if (button == NSAlertThirdButtonReturn) { 207 | selectedRemotePath = sortedRemotePaths[2]; 208 | } 209 | } 210 | 211 | if (selectedRemotePath.length == 0) 212 | { 213 | LZLog(@"Unable to find github remote URL."); 214 | return nil; 215 | } 216 | 217 | NSString *fullUrl = [NSString stringWithFormat:@"https://%@", selectedRemotePath]; 218 | 219 | return fullUrl; 220 | } 221 | 222 | - (BOOL)isValid { 223 | if (!self.localPath) { 224 | return NO; 225 | } 226 | return YES; 227 | } 228 | 229 | @end 230 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubKitPlugin.h 3 | // GitHubKitPlugin 4 | // 5 | // Created by lzw on 15/10/22. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RIGPlugin : NSObject 12 | 13 | + (instancetype)shared; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGPlugin.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGPlugin.m 3 | // 4 | // Created by lzw on 15/10/22. 5 | // Copyright © 2015年 lzwjava. All rights reserved. 6 | // 7 | 8 | #import "RIGPlugin.h" 9 | #import "RIGSettingWindowController.h" 10 | #import "RIGConfig.h" 11 | #import "RIGUtils.h" 12 | #import "RIGGitRepo.h" 13 | #import "RIGSetting.h" 14 | 15 | id objc_getClass(const char* name); 16 | 17 | #define kRIGMenuToInsert @"Window" 18 | 19 | #define kPatternGitRemoteUrl @"{git_remote_url}" 20 | 21 | static Class DVTSourceTextViewClass; 22 | static Class IDEWorkspaceWindowControllerClass; 23 | 24 | @interface RIGPlugin() 25 | 26 | @property (nonatomic, strong) id ideWorkspaceWindow; 27 | @property (nonatomic, strong) id sourceTextView; 28 | @property (nonatomic, assign) NSUInteger selectionStartLineNumber; 29 | @property (nonatomic, assign) NSUInteger selectionEndLineNumber; 30 | @property (nonatomic, assign) BOOL useHTTPS; 31 | 32 | @property (nonatomic, strong) RIGSettingWindowController *setttingController; 33 | 34 | @property (nonatomic, strong) RIGGitRepo *gitRepo; 35 | 36 | @end 37 | 38 | @implementation RIGPlugin 39 | 40 | + (void)pluginDidLoad:(NSBundle *)plugin { 41 | DVTSourceTextViewClass = objc_getClass("DVTSourceTextView"); 42 | IDEWorkspaceWindowControllerClass = objc_getClass("IDEWorkspaceWindowController"); 43 | [self shared]; 44 | } 45 | 46 | #pragma mark - init 47 | 48 | - (instancetype)init { 49 | if (self = [super init]) { 50 | [self addNotification]; 51 | } 52 | return self; 53 | } 54 | 55 | + (instancetype)shared { 56 | static dispatch_once_t onceToken; 57 | static id instance = nil; 58 | dispatch_once(&onceToken, ^{ 59 | instance = [[self alloc] init]; 60 | }); 61 | return instance; 62 | } 63 | 64 | - (void)dealloc { 65 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 66 | } 67 | 68 | #pragma mark - Menu init 69 | 70 | - (BOOL)menuExists { 71 | NSMenuItem *windowMenuItem = [[NSApp mainMenu] itemWithTitle:kRIGMenuToInsert]; 72 | NSMenuItem *menu = [windowMenuItem.submenu itemWithTitle:@"Reveal In GitHub"]; 73 | return menu != nil; 74 | } 75 | 76 | - (NSMenu *)githubMenu 77 | { 78 | NSMenuItem *windowMenuItem = [[NSApp mainMenu] itemWithTitle:kRIGMenuToInsert]; 79 | 80 | [[windowMenuItem submenu] addItem:[NSMenuItem separatorItem]]; 81 | NSMenuItem *githubMenuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:@"Reveal In GitHub" action:NULL keyEquivalent:@""]; 82 | githubMenuItem.enabled = YES; 83 | githubMenuItem.submenu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@"Reveal In GitHub"]; 84 | [windowMenuItem.submenu addItem:githubMenuItem]; 85 | 86 | return githubMenuItem.submenu; 87 | } 88 | 89 | - (void)applicationDidFinishLaunching:(NSNotification *)noti { 90 | [self addMenu]; 91 | } 92 | 93 | - (void)addMenu { 94 | if ([self menuExists]) { 95 | return; 96 | } 97 | 98 | NSMenu *githubMenu = [self githubMenu]; 99 | 100 | NSMenuItem *settings = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:@"Settings" action:@selector(showSettingWindow:) keyEquivalent:@"S"]; 101 | [settings setKeyEquivalentModifierMask:NSCommandKeyMask | NSControlKeyMask]; 102 | settings.target = self; 103 | [githubMenu addItem:settings]; 104 | 105 | NSArray *configs = [RIGSetting setting].configs; 106 | for (RIGConfig *config in configs) { 107 | NSString *keyEquivalent = config.lastKey; 108 | if (keyEquivalent == nil) { 109 | keyEquivalent = @""; 110 | } 111 | NSMenuItem *configItem = [[NSMenuItem alloc] initWithTitle:config.menuTitle action:@selector(customMenusClicked:) keyEquivalent:keyEquivalent]; 112 | if (keyEquivalent.length > 0) { 113 | [configItem setKeyEquivalentModifierMask:NSCommandKeyMask | NSControlKeyMask]; 114 | } 115 | configItem.target = self; 116 | [githubMenu addItem:configItem]; 117 | } 118 | } 119 | 120 | #pragma mark - Notification and Selectors 121 | 122 | - (void)addNotification { 123 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 124 | [nc addObserver:self selector:@selector(applicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:nil]; 125 | 126 | [nc addObserver:self selector:@selector(sourceTextViewSelectionDidChange:) name:NSTextViewDidChangeSelectionNotification object:nil]; 127 | 128 | [nc addObserver:self selector:@selector(fetchActiveIDEWorkspaceWindow:) name:NSWindowDidUpdateNotification object:nil]; 129 | } 130 | 131 | - (void)sourceTextViewSelectionDidChange:(NSNotification *)notification { 132 | id view = [notification object]; 133 | if ([view isMemberOfClass:DVTSourceTextViewClass]) 134 | { 135 | self.sourceTextView = view; 136 | } 137 | } 138 | 139 | - (void)fetchActiveIDEWorkspaceWindow:(NSNotification *)notification { 140 | id window = [notification object]; 141 | if ([window isKindOfClass:[NSWindow class]] && [window isMainWindow]) 142 | { 143 | self.ideWorkspaceWindow = window; 144 | } 145 | } 146 | 147 | #pragma mark - Xcode Part 148 | 149 | - (NSURL *)activeDocument 150 | { 151 | NSArray *windows = [IDEWorkspaceWindowControllerClass valueForKey:@"workspaceWindowControllers"]; 152 | for (id workspaceWindowController in windows) 153 | { 154 | if ([workspaceWindowController valueForKey:@"workspaceWindow"] == self.ideWorkspaceWindow || windows.count == 1) 155 | { 156 | id document = [[workspaceWindowController valueForKey:@"editorArea"] valueForKey:@"primaryEditorDocument"]; 157 | return [document fileURL]; 158 | } 159 | } 160 | 161 | return nil; 162 | } 163 | 164 | - (void)findSelection { 165 | if (self.sourceTextView == nil) { 166 | return; 167 | } 168 | NSRange selectedRange = [self.sourceTextView selectedRange]; 169 | NSString *sourceTextUntilSelection = [[self.sourceTextView string] substringWithRange:NSMakeRange(0, selectedRange.location)]; 170 | 171 | self.selectionStartLineNumber = [[sourceTextUntilSelection componentsSeparatedByCharactersInSet: 172 | [NSCharacterSet newlineCharacterSet]] count]; 173 | 174 | NSString *sourceTextSelection = [[self.sourceTextView string] substringWithRange:selectedRange]; 175 | NSUInteger selectedLines = [[sourceTextSelection componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] count]; 176 | self.selectionEndLineNumber = self.selectionStartLineNumber + (selectedLines > 1 ? selectedLines - 1 : 0); 177 | } 178 | 179 | - (NSString *)selectedLineString { 180 | NSRange selectedRange = [self.sourceTextView selectedRange]; 181 | NSString *sourceTextUntilSelection = [[self.sourceTextView string] substringWithRange:NSMakeRange(0, selectedRange.location)]; 182 | 183 | NSArray *components = [sourceTextUntilSelection componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; 184 | NSInteger selectedLineNumber = components.count; 185 | 186 | NSString *lineString = [[[self.sourceTextView string] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] objectAtIndex:selectedLineNumber - 1]; 187 | return lineString; 188 | } 189 | 190 | - (void)openUrl:(NSString *)url { 191 | NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:@[url]]; 192 | [task waitUntilExit]; 193 | if (![task isRunning]) { 194 | int status = [task terminationStatus]; 195 | if (status != 0 ) { 196 | NSModalResponse res = [RIGUtils showMessage:[NSString stringWithFormat:@"Cloud not open %@. It might be a bug. Please report it. Thanks.", url]]; 197 | if (res == NSAlertFirstButtonReturn) { 198 | [NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:@[@"https://github.com/lzwjava/Reveal-In-GitHub/issues/new"]]; 199 | } 200 | } 201 | } 202 | } 203 | 204 | #pragma mark - Show Settings 205 | 206 | - (void)showSettingWindow:(id)sender { 207 | if (![self trySetGitRepo]) { 208 | return; 209 | } 210 | self.setttingController = [[RIGSettingWindowController alloc] initWithWindowNibName:@"RIGSettingWindowController"]; 211 | self.setttingController.gitRepo = self.gitRepo; 212 | [self.setttingController showWindow:self.setttingController]; 213 | } 214 | 215 | #pragma mark - Git Menu Actions 216 | 217 | - (NSDictionary *)currentRepoInfos { 218 | NSMutableDictionary *dict= [[NSMutableDictionary alloc] init]; 219 | NSString *gitRemoteUrl = [self.gitRepo remoteRepoUrl]; 220 | if (gitRemoteUrl) { 221 | [dict setObject:gitRemoteUrl forKey:kPatternGitRemoteUrl]; 222 | } 223 | NSString *commit = [self.gitRepo latestCommitHash]; 224 | if (commit) { 225 | [dict setObject:commit forKey:@"{commit}"]; 226 | NSString *filePath = [self.gitRepo filenameWithPathInCommit:commit]; 227 | if (filePath) { 228 | [dict setObject:filePath forKey:@"{file_path}"]; 229 | } 230 | } 231 | NSString *selection = [self selectionString]; 232 | if (selection) { 233 | [dict setObject:selection forKey:@"{selection}"]; 234 | } 235 | return dict; 236 | } 237 | 238 | - (NSString *)selectionString { 239 | [self findSelection]; 240 | 241 | NSUInteger start = self.selectionStartLineNumber; 242 | NSUInteger end = self.selectionEndLineNumber; 243 | 244 | if (start == end) { 245 | return [NSString stringWithFormat:@"L%ld", start]; 246 | } else { 247 | return [NSString stringWithFormat:@"L%ld-L%ld", start, end]; 248 | } 249 | } 250 | 251 | - (BOOL)trySetGitRepo { 252 | NSURL *activeDocumentURL = [self activeDocument]; 253 | if (activeDocumentURL == nil) { 254 | [RIGUtils showMessage:@"No file is opening now."]; 255 | return NO; 256 | } 257 | self.gitRepo = [[RIGGitRepo alloc] initWithDocumentURL:[self activeDocument]]; 258 | if (![self.gitRepo isValid]) { 259 | [RIGUtils showMessage:@"Could not get git repo from current file."]; 260 | return NO; 261 | } 262 | return YES; 263 | } 264 | 265 | - (void)customMenusClicked:(NSMenuItem *)menuItem { 266 | if (![self trySetGitRepo]) { 267 | return; 268 | } 269 | 270 | RIGConfig *currentConfig = nil; 271 | for (RIGConfig *confing in [RIGSetting setting].configs) { 272 | if ([confing.menuTitle isEqualToString:menuItem.title]) { 273 | currentConfig = confing; 274 | break; 275 | } 276 | } 277 | 278 | NSDictionary *infos = [self currentRepoInfos]; 279 | if (infos[kPatternGitRemoteUrl] == nil) { 280 | [RIGUtils showMessage:@"Cannot find a git remote."]; 281 | return; 282 | } 283 | NSMutableString *url = [[NSMutableString alloc] initWithString:currentConfig.pattern]; 284 | for (NSString *key in [infos allKeys]) { 285 | NSString *value = [infos objectForKey:key]; 286 | [url replaceOccurrencesOfString:key withString:value options:NSLiteralSearch range:NSMakeRange(0, url.length)]; 287 | } 288 | [self openUrl:url]; 289 | } 290 | 291 | @end 292 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGSetting.h: -------------------------------------------------------------------------------- 1 | // 2 | // RIGSettingsManager.h 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/31. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RIGSetting : NSObject 12 | 13 | + (RIGSetting *)setting; 14 | + (RIGSetting *)settingForGitPath:(NSString *)gitPath; 15 | 16 | @property (nonatomic, strong) NSArray *configs; 17 | @property (nonatomic, strong) NSString *defaultRepo;; 18 | 19 | + (NSArray *)defaultConfigs; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGSetting.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGSettingsManager.m 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/31. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import "RIGSetting.h" 10 | #import "RIGConfig.h" 11 | 12 | #define kRIGDefaultRepo @"com.lzwjava.reveal-in-github.defaultRepo" 13 | #define kRIGConfigs @"com.lzwjava.reveal-in-github.configs" 14 | 15 | @interface RIGSetting() 16 | 17 | @property (nonatomic, strong) NSString *gitPath; 18 | 19 | @end 20 | 21 | @implementation RIGSetting 22 | 23 | + (RIGSetting *)setting { 24 | static dispatch_once_t once; 25 | static RIGSetting *defaultSetting; 26 | dispatch_once(&once, ^ { 27 | defaultSetting = [[RIGSetting alloc] init]; 28 | 29 | NSDictionary *defaults = @{kRIGConfigs: [[self class] dictsForConfigs:[[self class] defaultConfigs]]}; 30 | [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; 31 | }); 32 | return defaultSetting; 33 | } 34 | 35 | + (RIGSetting *)settingForGitPath:(NSString *)gitPath { 36 | RIGSetting *setting = [[RIGSetting alloc] init]; 37 | setting.gitPath = gitPath; 38 | return setting; 39 | } 40 | 41 | - (NSArray *)configs { 42 | NSArray *configDicts = [[NSUserDefaults standardUserDefaults] objectForKey:kRIGConfigs]; 43 | NSMutableArray *configs = [NSMutableArray array]; 44 | for (NSDictionary *configDict in configDicts) { 45 | RIGConfig *config = [[RIGConfig alloc] initWithDictionary:configDict]; 46 | [configs addObject:config]; 47 | } 48 | return configs; 49 | } 50 | 51 | - (void)setConfigs:(NSArray *)configs { 52 | NSArray *dicts = [[self class] dictsForConfigs:configs]; 53 | [[NSUserDefaults standardUserDefaults] setObject:dicts forKey:kRIGConfigs]; 54 | [[NSUserDefaults standardUserDefaults] synchronize]; 55 | } 56 | 57 | + (NSArray *)dictsForConfigs:(NSArray *)configs { 58 | NSMutableArray *dicts = [NSMutableArray array]; 59 | for (RIGConfig *config in configs) { 60 | NSDictionary *dict = [config dictionary]; 61 | if (dict.count > 0) { 62 | [dicts addObject:dict]; 63 | } 64 | } 65 | return dicts; 66 | } 67 | 68 | + (NSArray *)defaultConfigs { 69 | RIGConfig *config1 = [RIGConfig configWithMenuTitle:@"Repo" lastKey:@"R" pattern:@"{git_remote_url}"]; 70 | 71 | RIGConfig *config2 = [RIGConfig configWithMenuTitle:@"Issues" lastKey:@"I" pattern:@"{git_remote_url}/issues"]; 72 | 73 | RIGConfig *config3 = [RIGConfig configWithMenuTitle:@"PRs" lastKey:@"P" pattern:@"{git_remote_url}/pulls"]; 74 | 75 | RIGConfig *config4 = [RIGConfig configWithMenuTitle:@"Quick File" lastKey:@"Q" pattern:@"{git_remote_url}/blob/{commit}/{file_path}#{selection}"]; 76 | 77 | RIGConfig *config5 = [RIGConfig configWithMenuTitle:@"List History" lastKey:@"L" pattern:@"{git_remote_url}/commits/{commit}/{file_path}"]; 78 | 79 | RIGConfig *config6 = [RIGConfig configWithMenuTitle:@"Blame" lastKey:@"B" pattern:@"{git_remote_url}/blame/{commit}/{file_path}#{selection}"]; 80 | 81 | RIGConfig *config7 = [RIGConfig configWithMenuTitle:@"Notifications" lastKey:@"N" pattern:@"{git_remote_url}/notifications?all=1"]; 82 | 83 | return @[config1, config2, config3, config4, config5, config6, config7]; 84 | } 85 | 86 | #pragma mark - 87 | 88 | - (NSString *)defaultRepoKey { 89 | return [NSString stringWithFormat:@"%@:%@", kRIGDefaultRepo, self.gitPath]; 90 | } 91 | 92 | - (void)setDefaultRepo:(NSString *)defaultRepo { 93 | NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; 94 | if (defaultRepo == nil) { 95 | [ud removeObjectForKey:[self defaultRepoKey]]; 96 | } else { 97 | [ud setObject:defaultRepo forKey:[self defaultRepoKey]]; 98 | } 99 | [ud synchronize]; 100 | } 101 | 102 | - (NSString *)defaultRepo { 103 | return [[NSUserDefaults standardUserDefaults] stringForKey:[self defaultRepoKey]]; 104 | } 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGSettingWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RIGSettingWindowController.h 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/28. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RIGGitRepo.h" 11 | 12 | @interface RIGSettingWindowController : NSWindowController 13 | 14 | @property (nonatomic, strong) RIGGitRepo *gitRepo; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGSettingWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGSettingWindowController.m 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/28. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import "RIGSettingWindowController.h" 10 | #import "RIGConfigCellsView.h" 11 | #import "RIGConfig.h" 12 | #import "RIGPlugin.h" 13 | #import "RIGUtils.h" 14 | #import "RIGSetting.h" 15 | 16 | #define kOutterXMargin 0 17 | #define kOutterYMargin 0 18 | 19 | @interface RIGSettingWindowController () 20 | 21 | @property (nonatomic, strong) NSArray *configs; 22 | @property (nonatomic, strong) RIGConfigCellsView *configCellsView; 23 | @property (weak) IBOutlet NSView *mainView; 24 | @property (weak) IBOutlet NSView *configsView; 25 | 26 | @end 27 | 28 | @implementation RIGSettingWindowController 29 | 30 | - (void)awakeFromNib { 31 | [super awakeFromNib]; 32 | } 33 | 34 | - (void)windowDidLoad { 35 | [super windowDidLoad]; 36 | 37 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 38 | 39 | self.configs = [self displayConfigs]; 40 | 41 | self.configCellsView = [[RIGConfigCellsView alloc] initWithFrame:CGRectMake(kOutterXMargin, kOutterYMargin, CGRectGetWidth(self.configsView.frame) - 2 * kOutterXMargin, [RIGConfigCellsView heightForConfigs:self.configs])]; 42 | self.configCellsView.configs = self.configs; 43 | [self.configsView addSubview:self.configCellsView]; 44 | // [self updateConfigsViewHeight]; 45 | [self.configCellsView reloadData]; 46 | } 47 | 48 | - (void)updateConfigsViewHeight { 49 | CGRect frame = self.configsView.frame; 50 | frame.size.height = CGRectGetHeight(self.configCellsView.frame); 51 | self.configsView.frame = frame; 52 | } 53 | 54 | - (NSMutableArray *)displayConfigs { 55 | NSMutableArray *configs = [NSMutableArray arrayWithArray:[RIGSetting setting].configs]; 56 | while (configs.count < 10) { 57 | RIGConfig *config = [[RIGConfig alloc] init]; 58 | config.menuTitle = @""; 59 | config.lastKey = @""; 60 | config.pattern = @""; 61 | [configs addObject:config]; 62 | } 63 | return configs; 64 | } 65 | 66 | - (void)reloadConfigs { 67 | self.configs = [self displayConfigs]; 68 | self.configCellsView.configs = self.configs; 69 | [self.configCellsView reloadData]; 70 | } 71 | 72 | - (BOOL)isValidConfigs:(NSArray *)configs { 73 | for (RIGConfig *config in configs) { 74 | if (![config isValid]) { 75 | return NO; 76 | } 77 | } 78 | return YES; 79 | } 80 | 81 | - (NSArray *)filteredConfigs { 82 | NSMutableArray *filtered = [NSMutableArray array]; 83 | NSArray *configs = self.configCellsView.configs; 84 | for (RIGConfig *config in configs) { 85 | if (config.menuTitle.length > 0 || config.lastKey.length > 0 || config.pattern.length > 0) { 86 | [filtered addObject:config]; 87 | } 88 | } 89 | return filtered; 90 | } 91 | 92 | - (IBAction)saveButtonClcked:(id)sender { 93 | NSArray *configs = [self filteredConfigs]; 94 | if (![self isValidConfigs:configs]) { 95 | [RIGUtils showMessage:@"Please complete the config, should at least have menuTitle and pattern."]; 96 | return; 97 | } 98 | [RIGSetting setting].configs = self.configCellsView.configs; 99 | [RIGUtils showMessage:@"Save succeed. Will Take effect when reopen Xcode."]; 100 | } 101 | 102 | - (IBAction)clearButtonClicked:(id)sender { 103 | RIGSetting *setting = [RIGSetting settingForGitPath:self.gitRepo.localPath]; 104 | NSString *defaultRepo = setting.defaultRepo; 105 | if (defaultRepo == nil) { 106 | [RIGUtils showMessage:@"There's no default repo setting."]; 107 | } else { 108 | setting.defaultRepo = nil; 109 | [RIGUtils showMessage:[NSString stringWithFormat:@"Succeed to clear current default repo(%@) setting. In the next time to open github, will ask you to select new default repo.", defaultRepo]]; 110 | } 111 | } 112 | 113 | - (IBAction)resetMenusButtonClicked:(id)sender { 114 | [[RIGSetting setting] setConfigs:[RIGSetting defaultConfigs]]; 115 | [self reloadConfigs]; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGSettingWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 42 | 43 | 44 | 45 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // RIGUtils.h 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/31. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #ifdef DEBUG 13 | # define LZLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) 14 | #else 15 | # define LZLog(fmt, ...) 16 | #endif 17 | 18 | @interface RIGUtils : NSObject 19 | 20 | + (NSModalResponse)showMessage:(NSString *)message; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Reveal-In-GitHub/RIGUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // RIGUtils.m 3 | // Reveal-In-GitHub 4 | // 5 | // Created by lzw on 15/10/31. 6 | // Copyright © 2015年 lzwjava. All rights reserved. 7 | // 8 | 9 | #import "RIGUtils.h" 10 | #import 11 | 12 | @implementation RIGUtils 13 | 14 | + (NSModalResponse)showMessage:(NSString *)message { 15 | NSAlert *alert = [[NSAlert alloc] init]; 16 | [alert addButtonWithTitle:@"OK"]; 17 | [alert setMessageText: message]; 18 | [alert setAlertStyle:NSWarningAlertStyle]; 19 | return [alert runModal]; 20 | } 21 | 22 | @end 23 | --------------------------------------------------------------------------------