├── .gitignore ├── JSFormatter ├── JSFormatter.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── JSFormatter.xcscheme └── JSFormatter │ ├── Info.plist │ ├── JSFormatter+helper.h │ ├── JSFormatter+helper.m │ ├── JSFormatter.h │ ├── JSFormatter.m │ ├── NSObject_Extension.h │ ├── NSObject_Extension.m │ └── XCFXcodePrivate.h ├── LICENSE ├── README.md └── screenshot.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | # Xcode 28 | .DS_Store 29 | build/ 30 | *.pbxuser 31 | !default.pbxuser 32 | *.mode1v3 33 | !default.mode1v3 34 | *.mode2v3 35 | !default.mode2v3 36 | *.perspectivev3 37 | !default.perspectivev3 38 | *.xcworkspace 39 | !default.xcworkspace 40 | xcuserdata 41 | profile 42 | *.moved-aside 43 | DerivedData 44 | .idea/ 45 | # Pods - for those of you who use CocoaPods 46 | Pods 47 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FB8796471B2C063D00DBEC35 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8796461B2C063D00DBEC35 /* AppKit.framework */; }; 11 | FB8796491B2C063D00DBEC35 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB8796481B2C063D00DBEC35 /* Foundation.framework */; }; 12 | FB87964E1B2C063D00DBEC35 /* JSFormatter.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = FB87964D1B2C063D00DBEC35 /* JSFormatter.xcscheme */; }; 13 | FB8796511B2C063D00DBEC35 /* JSFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FB8796501B2C063D00DBEC35 /* JSFormatter.m */; }; 14 | FB8796541B2C063D00DBEC35 /* NSObject_Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = FB8796531B2C063D00DBEC35 /* NSObject_Extension.m */; }; 15 | FB87965D1B2C0D6400DBEC35 /* JSFormatter+helper.m in Sources */ = {isa = PBXBuildFile; fileRef = FB87965C1B2C0D6400DBEC35 /* JSFormatter+helper.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | FB8796431B2C063D00DBEC35 /* JSFormatter.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JSFormatter.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | FB8796461B2C063D00DBEC35 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 21 | FB8796481B2C063D00DBEC35 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 22 | FB87964C1B2C063D00DBEC35 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | FB87964D1B2C063D00DBEC35 /* JSFormatter.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = JSFormatter.xcscheme; path = JSFormatter.xcodeproj/xcshareddata/xcschemes/JSFormatter.xcscheme; sourceTree = SOURCE_ROOT; }; 24 | FB87964F1B2C063D00DBEC35 /* JSFormatter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSFormatter.h; sourceTree = ""; }; 25 | FB8796501B2C063D00DBEC35 /* JSFormatter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JSFormatter.m; sourceTree = ""; }; 26 | FB8796521B2C063D00DBEC35 /* NSObject_Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSObject_Extension.h; sourceTree = ""; }; 27 | FB8796531B2C063D00DBEC35 /* NSObject_Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSObject_Extension.m; sourceTree = ""; }; 28 | FB87965A1B2C0C3200DBEC35 /* XCFXcodePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCFXcodePrivate.h; sourceTree = ""; }; 29 | FB87965B1B2C0D6400DBEC35 /* JSFormatter+helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSFormatter+helper.h"; sourceTree = ""; }; 30 | FB87965C1B2C0D6400DBEC35 /* JSFormatter+helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSFormatter+helper.m"; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | FB8796411B2C063D00DBEC35 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | FB8796471B2C063D00DBEC35 /* AppKit.framework in Frameworks */, 39 | FB8796491B2C063D00DBEC35 /* Foundation.framework in Frameworks */, 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | FB87963A1B2C063D00DBEC35 = { 47 | isa = PBXGroup; 48 | children = ( 49 | FB87964A1B2C063D00DBEC35 /* JSFormatter */, 50 | FB8796451B2C063D00DBEC35 /* Frameworks */, 51 | FB8796441B2C063D00DBEC35 /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | FB8796441B2C063D00DBEC35 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | FB8796431B2C063D00DBEC35 /* JSFormatter.xcplugin */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | FB8796451B2C063D00DBEC35 /* Frameworks */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | FB8796461B2C063D00DBEC35 /* AppKit.framework */, 67 | FB8796481B2C063D00DBEC35 /* Foundation.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | FB87964A1B2C063D00DBEC35 /* JSFormatter */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | FB87964F1B2C063D00DBEC35 /* JSFormatter.h */, 76 | FB8796501B2C063D00DBEC35 /* JSFormatter.m */, 77 | FB87965B1B2C0D6400DBEC35 /* JSFormatter+helper.h */, 78 | FB87965C1B2C0D6400DBEC35 /* JSFormatter+helper.m */, 79 | FB87965A1B2C0C3200DBEC35 /* XCFXcodePrivate.h */, 80 | FB8796521B2C063D00DBEC35 /* NSObject_Extension.h */, 81 | FB8796531B2C063D00DBEC35 /* NSObject_Extension.m */, 82 | FB87964B1B2C063D00DBEC35 /* Supporting Files */, 83 | ); 84 | path = JSFormatter; 85 | sourceTree = ""; 86 | }; 87 | FB87964B1B2C063D00DBEC35 /* Supporting Files */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | FB87964C1B2C063D00DBEC35 /* Info.plist */, 91 | FB87964D1B2C063D00DBEC35 /* JSFormatter.xcscheme */, 92 | ); 93 | name = "Supporting Files"; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXNativeTarget section */ 99 | FB8796421B2C063D00DBEC35 /* JSFormatter */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = FB8796571B2C063D00DBEC35 /* Build configuration list for PBXNativeTarget "JSFormatter" */; 102 | buildPhases = ( 103 | FB87963F1B2C063D00DBEC35 /* Sources */, 104 | FB8796401B2C063D00DBEC35 /* Resources */, 105 | FB8796411B2C063D00DBEC35 /* Frameworks */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | name = JSFormatter; 112 | productName = JSFormatter; 113 | productReference = FB8796431B2C063D00DBEC35 /* JSFormatter.xcplugin */; 114 | productType = "com.apple.product-type.bundle"; 115 | }; 116 | /* End PBXNativeTarget section */ 117 | 118 | /* Begin PBXProject section */ 119 | FB87963B1B2C063D00DBEC35 /* Project object */ = { 120 | isa = PBXProject; 121 | attributes = { 122 | LastUpgradeCheck = 0630; 123 | ORGANIZATIONNAME = zztx; 124 | TargetAttributes = { 125 | FB8796421B2C063D00DBEC35 = { 126 | CreatedOnToolsVersion = 6.3.1; 127 | }; 128 | }; 129 | }; 130 | buildConfigurationList = FB87963E1B2C063D00DBEC35 /* Build configuration list for PBXProject "JSFormatter" */; 131 | compatibilityVersion = "Xcode 3.2"; 132 | developmentRegion = English; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | ); 137 | mainGroup = FB87963A1B2C063D00DBEC35; 138 | productRefGroup = FB8796441B2C063D00DBEC35 /* Products */; 139 | projectDirPath = ""; 140 | projectRoot = ""; 141 | targets = ( 142 | FB8796421B2C063D00DBEC35 /* JSFormatter */, 143 | ); 144 | }; 145 | /* End PBXProject section */ 146 | 147 | /* Begin PBXResourcesBuildPhase section */ 148 | FB8796401B2C063D00DBEC35 /* Resources */ = { 149 | isa = PBXResourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | FB87964E1B2C063D00DBEC35 /* JSFormatter.xcscheme in Resources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXResourcesBuildPhase section */ 157 | 158 | /* Begin PBXSourcesBuildPhase section */ 159 | FB87963F1B2C063D00DBEC35 /* Sources */ = { 160 | isa = PBXSourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | FB8796511B2C063D00DBEC35 /* JSFormatter.m in Sources */, 164 | FB8796541B2C063D00DBEC35 /* NSObject_Extension.m in Sources */, 165 | FB87965D1B2C0D6400DBEC35 /* JSFormatter+helper.m in Sources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXSourcesBuildPhase section */ 170 | 171 | /* Begin XCBuildConfiguration section */ 172 | FB8796551B2C063D00DBEC35 /* Debug */ = { 173 | isa = XCBuildConfiguration; 174 | buildSettings = { 175 | ALWAYS_SEARCH_USER_PATHS = NO; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_CONSTANT_CONVERSION = YES; 182 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 183 | CLANG_WARN_EMPTY_BODY = YES; 184 | CLANG_WARN_ENUM_CONVERSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 187 | CLANG_WARN_UNREACHABLE_CODE = YES; 188 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 189 | COPY_PHASE_STRIP = NO; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | GCC_C_LANGUAGE_STANDARD = gnu99; 192 | GCC_DYNAMIC_NO_PIC = NO; 193 | GCC_NO_COMMON_BLOCKS = YES; 194 | GCC_OPTIMIZATION_LEVEL = 0; 195 | GCC_PREPROCESSOR_DEFINITIONS = ( 196 | "DEBUG=1", 197 | "$(inherited)", 198 | ); 199 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 200 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 201 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 202 | GCC_WARN_UNDECLARED_SELECTOR = YES; 203 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 204 | GCC_WARN_UNUSED_FUNCTION = YES; 205 | GCC_WARN_UNUSED_VARIABLE = YES; 206 | MTL_ENABLE_DEBUG_INFO = YES; 207 | ONLY_ACTIVE_ARCH = YES; 208 | }; 209 | name = Debug; 210 | }; 211 | FB8796561B2C063D00DBEC35 /* Release */ = { 212 | isa = XCBuildConfiguration; 213 | buildSettings = { 214 | ALWAYS_SEARCH_USER_PATHS = NO; 215 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 216 | CLANG_CXX_LIBRARY = "libc++"; 217 | CLANG_ENABLE_MODULES = YES; 218 | CLANG_ENABLE_OBJC_ARC = YES; 219 | CLANG_WARN_BOOL_CONVERSION = YES; 220 | CLANG_WARN_CONSTANT_CONVERSION = YES; 221 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 222 | CLANG_WARN_EMPTY_BODY = YES; 223 | CLANG_WARN_ENUM_CONVERSION = YES; 224 | CLANG_WARN_INT_CONVERSION = YES; 225 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 226 | CLANG_WARN_UNREACHABLE_CODE = YES; 227 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 228 | COPY_PHASE_STRIP = NO; 229 | ENABLE_NS_ASSERTIONS = NO; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu99; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 234 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 235 | GCC_WARN_UNDECLARED_SELECTOR = YES; 236 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 237 | GCC_WARN_UNUSED_FUNCTION = YES; 238 | GCC_WARN_UNUSED_VARIABLE = YES; 239 | MTL_ENABLE_DEBUG_INFO = NO; 240 | }; 241 | name = Release; 242 | }; 243 | FB8796581B2C063D00DBEC35 /* Debug */ = { 244 | isa = XCBuildConfiguration; 245 | buildSettings = { 246 | COMBINE_HIDPI_IMAGES = YES; 247 | DEPLOYMENT_LOCATION = YES; 248 | DSTROOT = "$(HOME)"; 249 | INFOPLIST_FILE = JSFormatter/Info.plist; 250 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 251 | MACOSX_DEPLOYMENT_TARGET = 10.10; 252 | PRODUCT_NAME = "$(TARGET_NAME)"; 253 | WRAPPER_EXTENSION = xcplugin; 254 | }; 255 | name = Debug; 256 | }; 257 | FB8796591B2C063D00DBEC35 /* Release */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | COMBINE_HIDPI_IMAGES = YES; 261 | DEPLOYMENT_LOCATION = YES; 262 | DSTROOT = "$(HOME)"; 263 | INFOPLIST_FILE = JSFormatter/Info.plist; 264 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 265 | MACOSX_DEPLOYMENT_TARGET = 10.10; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | WRAPPER_EXTENSION = xcplugin; 268 | }; 269 | name = Release; 270 | }; 271 | /* End XCBuildConfiguration section */ 272 | 273 | /* Begin XCConfigurationList section */ 274 | FB87963E1B2C063D00DBEC35 /* Build configuration list for PBXProject "JSFormatter" */ = { 275 | isa = XCConfigurationList; 276 | buildConfigurations = ( 277 | FB8796551B2C063D00DBEC35 /* Debug */, 278 | FB8796561B2C063D00DBEC35 /* Release */, 279 | ); 280 | defaultConfigurationIsVisible = 0; 281 | defaultConfigurationName = Release; 282 | }; 283 | FB8796571B2C063D00DBEC35 /* Build configuration list for PBXNativeTarget "JSFormatter" */ = { 284 | isa = XCConfigurationList; 285 | buildConfigurations = ( 286 | FB8796581B2C063D00DBEC35 /* Debug */, 287 | FB8796591B2C063D00DBEC35 /* Release */, 288 | ); 289 | defaultConfigurationIsVisible = 0; 290 | defaultConfigurationName = Release; 291 | }; 292 | /* End XCConfigurationList section */ 293 | }; 294 | rootObject = FB87963B1B2C063D00DBEC35 /* Project object */; 295 | } 296 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter.xcodeproj/xcshareddata/xcschemes/JSFormatter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 58 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.zztx.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | DVTPlugInCompatibilityUUIDs 26 | 27 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 28 | C4A681B0-4A26-480E-93EC-1218098B9AA0 29 | AD68E85B-441B-4301-B564-A45E4919A6AD 30 | A16FF353-8441-459E-A50C-B071F53F51B7 31 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 32 | E969541F-E6F9-4D25-8158-72DC3545A6C6 33 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 34 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 35 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 36 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 37 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 38 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 39 | 7265231C-39B4-402C-89E1-16167C4CC990 40 | F41BD31E-2683-44B8-AE7F-5F09E919790E 41 | 42 | LSMinimumSystemVersion 43 | $(MACOSX_DEPLOYMENT_TARGET) 44 | NSPrincipalClass 45 | JSFormatter 46 | XC4Compatible 47 | 48 | XCPluginHasUI 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/JSFormatter+helper.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSFormatter+helper.h 3 | // JSFormatter 4 | // 5 | // Created by zx on 6/13/15. 6 | // Copyright (c) 2015 zztx. All rights reserved. 7 | // 8 | 9 | #import "JSFormatter.h" 10 | #import "XCFXcodePrivate.h" 11 | 12 | @interface JSFormatter (helper) 13 | 14 | + (IDEWorkspaceDocument *)currentWorkspaceDocument; 15 | 16 | + (NSTextView *)currentSourceCodeTextView; 17 | 18 | + (id)currentEditor; 19 | 20 | + (IDESourceCodeDocument *)currentSourceCodeDocument; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/JSFormatter+helper.m: -------------------------------------------------------------------------------- 1 | // 2 | // JSFormatter+helper.m 3 | // JSFormatter 4 | // 5 | // Created by zx on 6/13/15. 6 | // Copyright (c) 2015 zztx. All rights reserved. 7 | // 8 | 9 | #import "JSFormatter+helper.h" 10 | 11 | @implementation JSFormatter (helper) 12 | 13 | + (IDEWorkspaceDocument *)currentWorkspaceDocument 14 | { 15 | NSWindowController *currentWindowController = [[NSApp keyWindow] windowController]; 16 | id document = [currentWindowController document]; 17 | 18 | if (currentWindowController && [document isKindOfClass:NSClassFromString(@"IDEWorkspaceDocument")]) { 19 | return (IDEWorkspaceDocument *)document; 20 | } 21 | return nil; 22 | } 23 | 24 | + (NSTextView *)currentSourceCodeTextView 25 | { 26 | if ([[self currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) { 27 | IDESourceCodeEditor *editor = [self currentEditor]; 28 | return editor.textView; 29 | } 30 | 31 | if ([[self currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) { 32 | IDESourceCodeComparisonEditor *editor = [self currentEditor]; 33 | return editor.keyTextView; 34 | } 35 | 36 | return nil; 37 | } 38 | 39 | + (id)currentEditor 40 | { 41 | NSWindowController *currentWindowController = [[NSApp keyWindow] windowController]; 42 | 43 | if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { 44 | IDEWorkspaceWindowController *workspaceController = (IDEWorkspaceWindowController *)currentWindowController; 45 | IDEEditorArea *editorArea = [workspaceController editorArea]; 46 | IDEEditorContext *editorContext = [editorArea lastActiveEditorContext]; 47 | return [editorContext editor]; 48 | } 49 | return nil; 50 | } 51 | 52 | + (IDESourceCodeDocument *)currentSourceCodeDocument 53 | { 54 | if ([[[self class] currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) { 55 | IDESourceCodeEditor *editor = [[self class] currentEditor]; 56 | return editor.sourceCodeDocument; 57 | } 58 | 59 | if ([[[self class] currentEditor] isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) { 60 | IDESourceCodeComparisonEditor *editor = [[self class] currentEditor]; 61 | 62 | if ([[editor primaryDocument] isKindOfClass:NSClassFromString(@"IDESourceCodeDocument")]) { 63 | IDESourceCodeDocument *document = (IDESourceCodeDocument *)editor.primaryDocument; 64 | return document; 65 | } 66 | } 67 | 68 | return nil; 69 | } 70 | 71 | + (void)formatDocument:(IDESourceCodeDocument *)document withError:(NSError **)outError 72 | { 73 | NSTextView *textView = [self currentSourceCodeTextView]; 74 | 75 | DVTSourceTextStorage *textStorage = [document textStorage]; 76 | 77 | // We try to restore the original cursor position after the uncrustification. We compute a percentage value 78 | // expressing the actual selected line compared to the total number of lines of the document. After the uncrustification, 79 | // we restore the position taking into account the modified number of lines of the document. 80 | 81 | NSRange originalCharacterRange = [textView selectedRange]; 82 | NSRange originalLineRange = [textStorage lineRangeForCharacterRange:originalCharacterRange]; 83 | NSRange originalDocumentLineRange = [textStorage lineRangeForCharacterRange:NSMakeRange(0, textStorage.string.length)]; 84 | 85 | CGFloat verticalRelativePosition = (CGFloat)originalLineRange.location / (CGFloat)originalDocumentLineRange.length; 86 | 87 | IDEWorkspace *currentWorkspace = [self currentWorkspaceDocument].workspace; 88 | 89 | [self formatCodeOfDocument:document inWorkspace:currentWorkspace error:outError]; 90 | 91 | NSRange newDocumentLineRange = [textStorage lineRangeForCharacterRange:NSMakeRange(0, textStorage.string.length)]; 92 | NSUInteger restoredLine = roundf(verticalRelativePosition * (CGFloat)newDocumentLineRange.length); 93 | 94 | NSRange newCharacterRange = NSMakeRange(0, 0); 95 | 96 | newCharacterRange = [textStorage characterRangeForLineRange:NSMakeRange(restoredLine, 0)]; 97 | 98 | // If the selected line didn't change, we try to restore the initial cursor position. 99 | 100 | if (originalLineRange.location == restoredLine && NSMaxRange(originalCharacterRange) < textStorage.string.length) { 101 | newCharacterRange = originalCharacterRange; 102 | } 103 | 104 | if (newCharacterRange.location < textStorage.string.length) { 105 | [[self currentSourceCodeTextView] setSelectedRanges:@[[NSValue valueWithRange:newCharacterRange]]]; 106 | [textView scrollRangeToVisible:newCharacterRange]; 107 | } 108 | } 109 | 110 | + (BOOL)formatCodeOfDocument:(IDESourceCodeDocument *)document inWorkspace:(IDEWorkspace *)workspace error:(NSError **)outError 111 | { 112 | return YES; 113 | // NSError *error = nil; 114 | // 115 | // DVTSourceTextStorage *textStorage = [document textStorage]; 116 | // 117 | // NSString *originalString = [NSString stringWithString:textStorage.string]; 118 | // 119 | // if (textStorage.string.length > 0) { 120 | // CFOFormatter *formatter = [[self class] formatterForString:textStorage.string presentedURL:document.fileURL error:&error]; 121 | // NSString *formattedCode = [formatter stringByFormattingInputWithError:&error]; 122 | // 123 | // if (formattedCode) { 124 | // [textStorage beginEditing]; 125 | // 126 | // if (![formattedCode isEqualToString:textStorage.string]) { 127 | // [textStorage replaceCharactersInRange:NSMakeRange(0, textStorage.string.length) withString:formattedCode withUndoManager:[document undoManager]]; 128 | // } 129 | // [self normalizeCodeAtRange:NSMakeRange(0, textStorage.string.length) document:document]; 130 | // [textStorage endEditing]; 131 | // } 132 | // } 133 | // 134 | // if (error && outError) { 135 | // *outError = error; 136 | // } 137 | // 138 | // BOOL codeHasChanged = (originalString && ![originalString isEqualToString:textStorage.string]); 139 | // return codeHasChanged; 140 | } 141 | 142 | 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/JSFormatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSFormatter.h 3 | // JSFormatter 4 | // 5 | // Created by zx on 6/13/15. 6 | // Copyright (c) 2015 zztx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class JSFormatter; 12 | 13 | static JSFormatter *sharedPlugin; 14 | 15 | @interface JSFormatter : NSObject 16 | 17 | + (instancetype)sharedPlugin; 18 | - (id)initWithBundle:(NSBundle *)plugin; 19 | 20 | @property (nonatomic, strong, readonly) NSBundle* bundle; 21 | @end -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/JSFormatter.m: -------------------------------------------------------------------------------- 1 | // 2 | // JSFormatter.m 3 | // JSFormatter 4 | // 5 | // Created by zx on 6/13/15. 6 | // Copyright (c) 2015 zztx. All rights reserved. 7 | // 8 | 9 | #import "JSFormatter.h" 10 | #import "XCFXcodePrivate.h" 11 | #import "JSFormatter+helper.h" 12 | 13 | typedef NS_ENUM(NSInteger, JSFormatterFileType) { 14 | JSFormatterFileTypeJS, 15 | JSFormatterFileTypeHTML, 16 | JSFormatterFileTypeCSS 17 | }; 18 | 19 | @interface JSFormatter () 20 | 21 | @property (nonatomic, strong, readwrite) NSBundle *bundle; 22 | 23 | @end 24 | 25 | @implementation JSFormatter 26 | 27 | + (instancetype)sharedPlugin { 28 | return sharedPlugin; 29 | } 30 | 31 | - (id)initWithBundle:(NSBundle *)plugin { 32 | if (self = [super init]) { 33 | // reference to plugin's bundle, for resource access 34 | self.bundle = plugin; 35 | [[NSNotificationCenter defaultCenter] addObserver:self 36 | selector:@selector(didApplicationFinishLaunchingNotification:) 37 | name:NSApplicationDidFinishLaunchingNotification 38 | object:nil]; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)didApplicationFinishLaunchingNotification:(NSNotification *)noti { 45 | //removeObserver 46 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidFinishLaunchingNotification object:nil]; 47 | 48 | // Create menu items, initialize UI, etc. 49 | // Sample Menu Item: 50 | NSMenuItem *editMenu = [[NSApp mainMenu] itemWithTitle:@"Edit"]; 51 | 52 | if (editMenu) { 53 | [[editMenu submenu] addItem:[NSMenuItem separatorItem]]; 54 | NSMenuItem *jsFormatterItem = [NSMenuItem new]; 55 | jsFormatterItem.title = @"JSFormatter"; 56 | 57 | [[editMenu submenu] addItem:jsFormatterItem]; 58 | 59 | NSMenu *jsFormatterMenu = [NSMenu new]; 60 | jsFormatterItem.submenu = jsFormatterMenu; 61 | 62 | NSMenuItem *formatCurrentJSFileItem = [[NSMenuItem alloc]initWithTitle:@"Format Active JS File" action:@selector(formatCurrentJSFileItemPressed) keyEquivalent:@""]; 63 | formatCurrentJSFileItem.target = self; 64 | 65 | [jsFormatterMenu addItem:formatCurrentJSFileItem]; 66 | } 67 | } 68 | 69 | - (void)formatCurrentJSFileItemPressed { 70 | IDESourceCodeDocument *document = [[self class] currentSourceCodeDocument]; 71 | 72 | [[self class] formatDocument:document]; 73 | } 74 | 75 | + (BOOL)formatDocument:(IDESourceCodeDocument *)document { 76 | DVTSourceTextStorage *textStorage = [document textStorage]; 77 | NSString *originalString = [NSString stringWithString:textStorage.string]; 78 | 79 | if (textStorage.string.length > 0) { 80 | 81 | NSArray *types = @[@"js",@"html",@"css",@"json",@"htm"]; 82 | if ([types indexOfObject:document.fileURL.pathExtension] == NSNotFound) { 83 | //not support other file types 84 | NSAlert *alert = [[NSAlert alloc] init]; 85 | [alert setMessageText:@"Only support js\\html\\htm\\css\\json now"]; 86 | [alert runModal]; 87 | return NO; 88 | } 89 | 90 | NSString *formattedCode = [self formattedCodeOfString:textStorage.string pathExtension:document.fileURL.pathExtension]; 91 | 92 | if (formattedCode) { 93 | [textStorage beginEditing]; 94 | 95 | if (![formattedCode isEqualToString:textStorage.string]) { 96 | [textStorage replaceCharactersInRange:NSMakeRange(0, textStorage.string.length) withString:formattedCode withUndoManager:[document undoManager]]; 97 | } 98 | 99 | [textStorage endEditing]; 100 | } 101 | } 102 | 103 | BOOL codeHasChanged = (originalString && ![originalString isEqualToString:textStorage.string]); 104 | return codeHasChanged; 105 | } 106 | 107 | + (NSString *)formattedCodeOfString:(NSString *)string pathExtension:(NSString *)pathExtension { 108 | NSString *path = @"/tmp/out.tmp.$$"; 109 | [string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; 110 | 111 | //1 112 | NSTask *task = [[NSTask alloc] init]; 113 | 114 | //2 115 | task.launchPath = @"/usr/local/bin/node"; 116 | 117 | //init output pipe 118 | NSPipe *outputPipe = [NSPipe pipe]; 119 | [task setStandardOutput:outputPipe]; 120 | 121 | //3 122 | if ([pathExtension isEqualToString:@"json"]) { 123 | pathExtension = @"js"; 124 | } 125 | NSString *command = [NSString stringWithFormat:@"/usr/local/bin/%@-beautify",pathExtension]; 126 | task.arguments = @[command, path]; 127 | 128 | //4 129 | [task launch]; 130 | 131 | //5 132 | [task waitUntilExit]; 133 | 134 | NSFileHandle *read = [outputPipe fileHandleForReading]; 135 | NSData *dataRead = [read readDataToEndOfFile]; 136 | NSString *stringRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding]; 137 | NSLog(@"output: %@", stringRead); 138 | 139 | 140 | return stringRead; 141 | } 142 | 143 | - (void)dealloc { 144 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 145 | } 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/NSObject_Extension.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject_Extension.h 3 | // JSFormatter 4 | // 5 | // Created by zx on 6/13/15. 6 | // Copyright (c) 2015 zztx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (Xcode_Plugin_Template_Extension) 12 | 13 | + (void)pluginDidLoad:(NSBundle *)plugin; 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/NSObject_Extension.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject_Extension.m 3 | // JSFormatter 4 | // 5 | // Created by zx on 6/13/15. 6 | // Copyright (c) 2015 zztx. All rights reserved. 7 | // 8 | 9 | 10 | #import "NSObject_Extension.h" 11 | #import "JSFormatter.h" 12 | 13 | @implementation NSObject (Xcode_Plugin_Template_Extension) 14 | 15 | + (void)pluginDidLoad:(NSBundle *)plugin 16 | { 17 | static dispatch_once_t onceToken; 18 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 19 | if ([currentApplicationName isEqual:@"Xcode"]) { 20 | dispatch_once(&onceToken, ^{ 21 | sharedPlugin = [[JSFormatter alloc] initWithBundle:plugin]; 22 | }); 23 | } 24 | } 25 | @end 26 | -------------------------------------------------------------------------------- /JSFormatter/JSFormatter/XCFXcodePrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Benoît on 11/01/14. 3 | // Copyright (c) 2014 Pragmatic Code. All rights reserved. 4 | // 5 | 6 | #import 7 | 8 | @interface DVTTextDocumentLocation : NSObject 9 | @property (readonly) NSRange characterRange; 10 | @property (readonly) NSRange lineRange; 11 | @end 12 | 13 | @interface DVTTextPreferences : NSObject 14 | + (id)preferences; 15 | @property BOOL trimWhitespaceOnlyLines; 16 | @property BOOL trimTrailingWhitespace; 17 | @property BOOL useSyntaxAwareIndenting; 18 | @end 19 | 20 | @interface DVTSourceTextStorage : NSTextStorage 21 | - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)string withUndoManager:(id)undoManager; 22 | - (NSRange)lineRangeForCharacterRange:(NSRange)range; 23 | - (NSRange)characterRangeForLineRange:(NSRange)range; 24 | - (void)indentCharacterRange:(NSRange)range undoManager:(id)undoManager; 25 | @end 26 | 27 | @interface DVTFileDataType : NSObject 28 | @property (readonly) NSString *identifier; 29 | @end 30 | 31 | @interface DVTFilePath : NSObject 32 | @property (readonly) NSURL *fileURL; 33 | @property (readonly) DVTFileDataType *fileDataTypePresumed; 34 | @end 35 | 36 | @interface IDEContainerItem : NSObject 37 | @property (readonly) DVTFilePath *resolvedFilePath; 38 | @end 39 | 40 | @interface IDEGroup : IDEContainerItem 41 | 42 | @end 43 | 44 | @interface IDEFileReference : IDEContainerItem 45 | 46 | @end 47 | 48 | @interface IDENavigableItem : NSObject 49 | @property (readonly) IDENavigableItem *parentItem; 50 | @property (readonly) NSArray *childItems; 51 | @property (readonly) id representedObject; 52 | @end 53 | 54 | @interface IDEFileNavigableItem : IDENavigableItem 55 | @property (readonly) DVTFileDataType *documentType; 56 | @property (readonly) NSURL *fileURL; 57 | @end 58 | 59 | @interface IDEGroupNavigableItem : IDENavigableItem 60 | @property (readonly) IDEGroup *group; 61 | @end 62 | 63 | @interface IDEStructureNavigator : NSObject 64 | @property (retain) NSArray *selectedObjects; 65 | @end 66 | 67 | @interface IDENavigableItemCoordinator : NSObject 68 | - (id)structureNavigableItemForDocumentURL:(id)arg1 inWorkspace:(id)arg2 error:(id *)arg3; 69 | @end 70 | 71 | @interface IDENavigatorArea : NSObject 72 | - (id)currentNavigator; 73 | @end 74 | 75 | @interface IDEWorkspaceTabController : NSObject 76 | @property (readonly) IDENavigatorArea *navigatorArea; 77 | @end 78 | 79 | @interface IDEDocumentController : NSDocumentController 80 | + (id)editorDocumentForNavigableItem:(id)arg1; 81 | + (id)retainedEditorDocumentForNavigableItem:(id)arg1 error:(id *)arg2; 82 | + (void)releaseEditorDocument:(id)arg1; 83 | @end 84 | 85 | @interface IDESourceCodeDocument : NSDocument 86 | - (DVTSourceTextStorage *)textStorage; 87 | - (NSUndoManager *)undoManager; 88 | @end 89 | 90 | @interface IDESourceCodeComparisonEditor : NSObject 91 | @property (readonly) NSTextView *keyTextView; 92 | @property (retain) NSDocument *primaryDocument; 93 | @end 94 | 95 | @interface IDESourceCodeEditor : NSObject 96 | @property (retain) NSTextView *textView; 97 | - (IDESourceCodeDocument *)sourceCodeDocument; 98 | @end 99 | 100 | @interface IDEEditorContext : NSObject 101 | - (id)editor; // returns the current editor. If the editor is the code editor, the class is `IDESourceCodeEditor` 102 | @end 103 | 104 | @interface IDEEditorArea : NSObject 105 | - (IDEEditorContext *)lastActiveEditorContext; 106 | @end 107 | 108 | @interface IDEWorkspaceWindowController : NSObject 109 | @property (readonly) IDEWorkspaceTabController *activeWorkspaceTabController; 110 | - (IDEEditorArea *)editorArea; 111 | @end 112 | 113 | @interface IDEWorkspace : NSObject 114 | @property (readonly) DVTFilePath *representingFilePath; 115 | @end 116 | 117 | @interface IDEWorkspaceDocument : NSDocument 118 | @property (readonly) IDEWorkspace *workspace; 119 | @end 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ZX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # No longer maintained 2 | 3 | # JSFormatter-Xcode 4 | Xcode plug to format js html css files using [js-beautify](https://github.com/beautify-web/js-beautify) 5 | 6 | ## Requirement 7 | * [Node.js](https://nodejs.org/) 8 | * [js-beautify](https://github.com/beautify-web/js-beautify) 9 | * Xcode 6.0+ on OS X 10.10+. 10 | * Xcode 8 [use `update_xcode_plugins`](https://github.com/inket/update_xcode_plugins) 11 | 12 | ## Screenshot 13 | ![image](https://raw.githubusercontent.com/bumaociyuan/JSFormatter-Xcode/master/screenshot.gif) 14 | 15 | ##Support Extension 16 | * js 17 | * css 18 | * html\htm 19 | * json 20 | 21 | ## Installation 22 | 23 | #### Build from source code 24 | 25 | * Build the Xcode project. The plug-in will automatically be installed in ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins. 26 | * Relaunch Xcode. 27 | 28 | To uninstall, just remove the plugin from ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins and restart Xcode. 29 | 30 | #### Install Via [Alcatraz](http://alcatraz.io/) 31 | 32 | * Install Alcatraz. 33 | * Search `JSFormatter` click the icon on left to install. 34 | 35 | ## Get Xcode UUID 36 | `defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID` 37 | 38 | ## How does it work? 39 | All the commands are in the menu `Edit` > `JSFormatter`. 40 | Click `Format Active JS File` to format 41 | 42 | ## Shortcut 43 | You can create keyboard shortcuts for the menu items in the [Keyboard Preferences](http://support.apple.com/kb/ph3957) of OS X System Preferences. 44 | 45 | ## Thanks 46 | Thanks to the [BBUncrustifyPlugin-Xcode](https://github.com/benoitsan/BBUncrustifyPlugin-Xcode) since I used some code from it. 47 | 48 | ## TODO 49 | 50 | 1. ~~Install via Alcatraz~~ 51 | 2. Add `Format Selected JS Files` 52 | 3. Add `Format Selected JS Lines` 53 | 4. ~~Xcode 7 Support~~ 54 | 55 | ## License 56 | The MIT License (MIT) 57 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bumaociyuan/JSFormatter-Xcode/fb06a2d22900c0a4ce58827314a3317e0e6dd0a4/screenshot.gif --------------------------------------------------------------------------------