├── .gitignore ├── LICENSE ├── README.md ├── Xcode Multi Edit.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── XcodeMultiEdit ├── Info.plist ├── MultiEdit.h ├── MultiEdit.m ├── MultiEditContext.h ├── MultiEditContext.m ├── MultiEditView.h ├── MultiEditView.m └── XCPrivateMethods.h └── img ├── one.gif ├── static.png └── two.gif /.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 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | #Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Timothy Edwards 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 |

NOTE: Since Apple removed plugin injection in Xcode 8, this plugin is no longer functional.

2 | 3 | # Xcode-Multi-Edit-Plugin 4 | 5 |

Overview

6 | 7 | This is the public repo for the Multi Edit Xcode plugin. This plugin allows developers to quickly and easily edit multiple instances of a selected term in a source document. This is a feature borrowed from many professional text editors, such as Sublime Text. 8 | 9 | Examples: 10 | 11 |
12 | 13 |
14 | 15 | ![](img/one.gif) 16 | 17 |
18 | 19 |
20 | 21 | ![](img/two.gif) 22 | 23 |

Installation

24 | 25 | The plugin can be installed easily through the [Alcatraz Plugin Manager](http://alcatraz.io) 26 | 27 | for Xcode, or installed manually by simply downloading or cloning the repo and running the project. 28 | 29 |

Usage

30 | 31 | Shift-Option-A: Include next instance of the selected term
32 | 33 | Shift-Option-Z: Undo the last selection
34 | 35 | Shift-Option-S: Undo the last selection and select the next instance
36 | 37 | Then simply type out your replacement text and hit enter. 38 | 39 |

Requirements

40 | 41 |
Xcode v7.0+
42 | 43 | May work on earlier versions but has not been tested. If you experience problems try updating to the latest version. 44 | 45 |

Contributions

46 | 47 | This plugin is not heavily tested and so if you encounter errors or problems please open an issue. We welcome pull requests, so if you feel like contributing get involved! 48 | 49 |

Author

50 | 51 | Developed and maintained by Tim Edwards ([@timwredwards](https://twitter.com/timwredwards)) 52 | -------------------------------------------------------------------------------- /Xcode Multi Edit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1C91B0ED1BCEA3A600A6EBD7 /* MultiEditContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C91B0EC1BCEA3A600A6EBD7 /* MultiEditContext.m */; }; 11 | 1CA36E821BCDFE28009503E6 /* MultiEditView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CA36E811BCDFE28009503E6 /* MultiEditView.m */; }; 12 | 226268A11AA55B2500ABBBB2 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 226268A01AA55B2500ABBBB2 /* AppKit.framework */; }; 13 | 226268A31AA55B2500ABBBB2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 226268A21AA55B2500ABBBB2 /* Foundation.framework */; }; 14 | 226268AB1AA55B2500ABBBB2 /* MultiEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 226268AA1AA55B2500ABBBB2 /* MultiEdit.m */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 1C8976BC1BCC29590026CA62 /* XCPrivateMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCPrivateMethods.h; sourceTree = ""; }; 19 | 1C91B0EB1BCEA3A600A6EBD7 /* MultiEditContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiEditContext.h; sourceTree = ""; }; 20 | 1C91B0EC1BCEA3A600A6EBD7 /* MultiEditContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultiEditContext.m; sourceTree = ""; }; 21 | 1C91B0EF1BCEA48700A6EBD7 /* MultiEdit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiEdit.h; sourceTree = ""; }; 22 | 1CA36E801BCDFE28009503E6 /* MultiEditView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiEditView.h; sourceTree = ""; }; 23 | 1CA36E811BCDFE28009503E6 /* MultiEditView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultiEditView.m; sourceTree = ""; }; 24 | 2262689D1AA55B2500ABBBB2 /* XcodeMultiEdit.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XcodeMultiEdit.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 226268A01AA55B2500ABBBB2 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 26 | 226268A21AA55B2500ABBBB2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 27 | 226268A61AA55B2500ABBBB2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 226268AA1AA55B2500ABBBB2 /* MultiEdit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MultiEdit.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 2262689B1AA55B2500ABBBB2 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | 226268A11AA55B2500ABBBB2 /* AppKit.framework in Frameworks */, 37 | 226268A31AA55B2500ABBBB2 /* Foundation.framework in Frameworks */, 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 226268941AA55B2500ABBBB2 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 226268A41AA55B2500ABBBB2 /* xcodeplugin */, 48 | 2262689F1AA55B2500ABBBB2 /* Frameworks */, 49 | 2262689E1AA55B2500ABBBB2 /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 2262689E1AA55B2500ABBBB2 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 2262689D1AA55B2500ABBBB2 /* XcodeMultiEdit.xcplugin */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 2262689F1AA55B2500ABBBB2 /* Frameworks */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 226268A01AA55B2500ABBBB2 /* AppKit.framework */, 65 | 226268A21AA55B2500ABBBB2 /* Foundation.framework */, 66 | ); 67 | name = Frameworks; 68 | sourceTree = ""; 69 | }; 70 | 226268A41AA55B2500ABBBB2 /* xcodeplugin */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 1C8976BC1BCC29590026CA62 /* XCPrivateMethods.h */, 74 | 1C91B0EF1BCEA48700A6EBD7 /* MultiEdit.h */, 75 | 226268AA1AA55B2500ABBBB2 /* MultiEdit.m */, 76 | 1C91B0EB1BCEA3A600A6EBD7 /* MultiEditContext.h */, 77 | 1C91B0EC1BCEA3A600A6EBD7 /* MultiEditContext.m */, 78 | 1CA36E801BCDFE28009503E6 /* MultiEditView.h */, 79 | 1CA36E811BCDFE28009503E6 /* MultiEditView.m */, 80 | 226268A51AA55B2500ABBBB2 /* Supporting Files */, 81 | ); 82 | name = xcodeplugin; 83 | path = XcodeMultiEdit; 84 | sourceTree = ""; 85 | }; 86 | 226268A51AA55B2500ABBBB2 /* Supporting Files */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 226268A61AA55B2500ABBBB2 /* Info.plist */, 90 | ); 91 | name = "Supporting Files"; 92 | sourceTree = ""; 93 | }; 94 | /* End PBXGroup section */ 95 | 96 | /* Begin PBXNativeTarget section */ 97 | 2262689C1AA55B2500ABBBB2 /* XcodeMultiEdit */ = { 98 | isa = PBXNativeTarget; 99 | buildConfigurationList = 226268AE1AA55B2500ABBBB2 /* Build configuration list for PBXNativeTarget "XcodeMultiEdit" */; 100 | buildPhases = ( 101 | 226268991AA55B2500ABBBB2 /* Sources */, 102 | 2262689A1AA55B2500ABBBB2 /* Resources */, 103 | 2262689B1AA55B2500ABBBB2 /* Frameworks */, 104 | ); 105 | buildRules = ( 106 | ); 107 | dependencies = ( 108 | ); 109 | name = XcodeMultiEdit; 110 | productName = xcodeplugin; 111 | productReference = 2262689D1AA55B2500ABBBB2 /* XcodeMultiEdit.xcplugin */; 112 | productType = "com.apple.product-type.bundle"; 113 | }; 114 | /* End PBXNativeTarget section */ 115 | 116 | /* Begin PBXProject section */ 117 | 226268951AA55B2500ABBBB2 /* Project object */ = { 118 | isa = PBXProject; 119 | attributes = { 120 | LastUpgradeCheck = 0700; 121 | ORGANIZATIONNAME = Tim; 122 | TargetAttributes = { 123 | 2262689C1AA55B2500ABBBB2 = { 124 | CreatedOnToolsVersion = 6.1.1; 125 | }; 126 | }; 127 | }; 128 | buildConfigurationList = 226268981AA55B2500ABBBB2 /* Build configuration list for PBXProject "Xcode Multi Edit" */; 129 | compatibilityVersion = "Xcode 3.2"; 130 | developmentRegion = English; 131 | hasScannedForEncodings = 0; 132 | knownRegions = ( 133 | en, 134 | ); 135 | mainGroup = 226268941AA55B2500ABBBB2; 136 | productRefGroup = 2262689E1AA55B2500ABBBB2 /* Products */; 137 | projectDirPath = ""; 138 | projectRoot = ""; 139 | targets = ( 140 | 2262689C1AA55B2500ABBBB2 /* XcodeMultiEdit */, 141 | ); 142 | }; 143 | /* End PBXProject section */ 144 | 145 | /* Begin PBXResourcesBuildPhase section */ 146 | 2262689A1AA55B2500ABBBB2 /* Resources */ = { 147 | isa = PBXResourcesBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXResourcesBuildPhase section */ 154 | 155 | /* Begin PBXSourcesBuildPhase section */ 156 | 226268991AA55B2500ABBBB2 /* Sources */ = { 157 | isa = PBXSourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | 1CA36E821BCDFE28009503E6 /* MultiEditView.m in Sources */, 161 | 1C91B0ED1BCEA3A600A6EBD7 /* MultiEditContext.m in Sources */, 162 | 226268AB1AA55B2500ABBBB2 /* MultiEdit.m in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin XCBuildConfiguration section */ 169 | 226268AC1AA55B2500ABBBB2 /* Debug */ = { 170 | isa = XCBuildConfiguration; 171 | buildSettings = { 172 | ALWAYS_SEARCH_USER_PATHS = NO; 173 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 174 | CLANG_CXX_LIBRARY = "libc++"; 175 | CLANG_ENABLE_MODULES = YES; 176 | CLANG_ENABLE_OBJC_ARC = YES; 177 | CLANG_WARN_BOOL_CONVERSION = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 180 | CLANG_WARN_EMPTY_BODY = YES; 181 | CLANG_WARN_ENUM_CONVERSION = YES; 182 | CLANG_WARN_INT_CONVERSION = YES; 183 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 184 | CLANG_WARN_UNREACHABLE_CODE = YES; 185 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 186 | COPY_PHASE_STRIP = NO; 187 | ENABLE_STRICT_OBJC_MSGSEND = YES; 188 | ENABLE_TESTABILITY = YES; 189 | GCC_C_LANGUAGE_STANDARD = gnu99; 190 | GCC_DYNAMIC_NO_PIC = NO; 191 | GCC_OPTIMIZATION_LEVEL = 0; 192 | GCC_PREPROCESSOR_DEFINITIONS = ( 193 | "DEBUG=1", 194 | "$(inherited)", 195 | ); 196 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 197 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 198 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 199 | GCC_WARN_UNDECLARED_SELECTOR = YES; 200 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 201 | GCC_WARN_UNUSED_FUNCTION = YES; 202 | GCC_WARN_UNUSED_VARIABLE = YES; 203 | MTL_ENABLE_DEBUG_INFO = YES; 204 | ONLY_ACTIVE_ARCH = YES; 205 | }; 206 | name = Debug; 207 | }; 208 | 226268AD1AA55B2500ABBBB2 /* Release */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 213 | CLANG_CXX_LIBRARY = "libc++"; 214 | CLANG_ENABLE_MODULES = YES; 215 | CLANG_ENABLE_OBJC_ARC = YES; 216 | CLANG_WARN_BOOL_CONVERSION = YES; 217 | CLANG_WARN_CONSTANT_CONVERSION = YES; 218 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INT_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_UNREACHABLE_CODE = YES; 224 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 225 | COPY_PHASE_STRIP = YES; 226 | ENABLE_NS_ASSERTIONS = NO; 227 | ENABLE_STRICT_OBJC_MSGSEND = YES; 228 | GCC_C_LANGUAGE_STANDARD = gnu99; 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | MTL_ENABLE_DEBUG_INFO = NO; 236 | }; 237 | name = Release; 238 | }; 239 | 226268AF1AA55B2500ABBBB2 /* Debug */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | COMBINE_HIDPI_IMAGES = YES; 243 | DEPLOYMENT_LOCATION = YES; 244 | DSTROOT = "$(HOME)"; 245 | GCC_WARN_UNUSED_VARIABLE = NO; 246 | INFOPLIST_FILE = XcodeMultiEdit/Info.plist; 247 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 248 | MACOSX_DEPLOYMENT_TARGET = 10.10; 249 | PRODUCT_BUNDLE_IDENTIFIER = "com.TimEdwards.$(PRODUCT_NAME:rfc1034identifier)"; 250 | PRODUCT_NAME = XcodeMultiEdit; 251 | WRAPPER_EXTENSION = xcplugin; 252 | }; 253 | name = Debug; 254 | }; 255 | 226268B01AA55B2500ABBBB2 /* Release */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | COMBINE_HIDPI_IMAGES = YES; 259 | DEPLOYMENT_LOCATION = YES; 260 | DSTROOT = "$(HOME)"; 261 | GCC_WARN_UNUSED_VARIABLE = NO; 262 | INFOPLIST_FILE = XcodeMultiEdit/Info.plist; 263 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 264 | MACOSX_DEPLOYMENT_TARGET = 10.10; 265 | PRODUCT_BUNDLE_IDENTIFIER = "com.TimEdwards.$(PRODUCT_NAME:rfc1034identifier)"; 266 | PRODUCT_NAME = XcodeMultiEdit; 267 | WRAPPER_EXTENSION = xcplugin; 268 | }; 269 | name = Release; 270 | }; 271 | /* End XCBuildConfiguration section */ 272 | 273 | /* Begin XCConfigurationList section */ 274 | 226268981AA55B2500ABBBB2 /* Build configuration list for PBXProject "Xcode Multi Edit" */ = { 275 | isa = XCConfigurationList; 276 | buildConfigurations = ( 277 | 226268AC1AA55B2500ABBBB2 /* Debug */, 278 | 226268AD1AA55B2500ABBBB2 /* Release */, 279 | ); 280 | defaultConfigurationIsVisible = 0; 281 | defaultConfigurationName = Release; 282 | }; 283 | 226268AE1AA55B2500ABBBB2 /* Build configuration list for PBXNativeTarget "XcodeMultiEdit" */ = { 284 | isa = XCConfigurationList; 285 | buildConfigurations = ( 286 | 226268AF1AA55B2500ABBBB2 /* Debug */, 287 | 226268B01AA55B2500ABBBB2 /* Release */, 288 | ); 289 | defaultConfigurationIsVisible = 0; 290 | defaultConfigurationName = Release; 291 | }; 292 | /* End XCConfigurationList section */ 293 | }; 294 | rootObject = 226268951AA55B2500ABBBB2 /* Project object */; 295 | } 296 | -------------------------------------------------------------------------------- /Xcode Multi Edit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XcodeMultiEdit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSPrincipalClass 28 | xcodeplugin 29 | XC4Compatible 30 | 31 | XCPluginHasUI 32 | 33 | DVTPlugInCompatibilityUUIDs 34 | 35 | FEC992CC-CA4A-4CFD-8881-77300FCB848A 36 | C4A681B0-4A26-480E-93EC-1218098B9AA0 37 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 38 | AD68E85B-441B-4301-B564-A45E4919A6AD 39 | 63FC1C47-140D-42B0-BB4D-A10B2D225574 40 | 37B30044-3B14-46BA-ABAA-F01000C27B63 41 | 640F884E-CE55-4B40-87C0-8869546CAB7A 42 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 43 | A16FF353-8441-459E-A50C-B071F53F51B7 44 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 45 | E969541F-E6F9-4D25-8158-72DC3545A6C6 46 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 47 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 48 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 49 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 50 | CC0D0F4F-05B3-431A-8F33-F84AFCB2C651 51 | 7265231C-39B4-402C-89E1-16167C4CC990 52 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /XcodeMultiEdit/MultiEdit.h: -------------------------------------------------------------------------------- 1 | // 2 | // xcodeplugin.h 3 | // xcodeplugin 4 | // 5 | // Created by Tim on 03/03/2015. 6 | // Copyright (c) 2015 Tim. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "XCPrivateMethods.h" 12 | 13 | #import "MultiEditContext.h" 14 | 15 | @interface xcodeplugin : NSObject 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /XcodeMultiEdit/MultiEdit.m: -------------------------------------------------------------------------------- 1 | // 2 | // xcodeplugin.m 3 | // xcodeplugin 4 | // 5 | // Created by Tim on 03/03/2015. 6 | // Copyright (c) 2015 Tim. All rights reserved. 7 | // 8 | 9 | #import "MultiEdit.h" 10 | 11 | #import 12 | 13 | static xcodeplugin *sharedPlugin; 14 | 15 | @interface xcodeplugin(){ 16 | NSMenu *multiEditSubmenu; 17 | } 18 | 19 | @property (nonatomic, strong, readwrite) NSBundle *bundle; 20 | 21 | @end 22 | 23 | static int kMultiEditContextKey; 24 | 25 | @implementation xcodeplugin 26 | 27 | + (void)pluginDidLoad:(NSBundle *)pluginBundle { 28 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 29 | 30 | static dispatch_once_t onceToken; 31 | if ([currentApplicationName isEqual:@"Xcode"]) { 32 | dispatch_once(&onceToken, ^{ 33 | sharedPlugin = [[self alloc] initWithBundle:pluginBundle]; // init once 34 | }); 35 | } 36 | } 37 | 38 | + (instancetype)sharedPlugin { 39 | return sharedPlugin; 40 | } 41 | 42 | -(id)initWithBundle:(NSBundle *)plugin{ 43 | if (self = [super init]) { 44 | // reference to plugin's bundle, for resource access 45 | self.bundle = plugin; 46 | [[NSNotificationCenter defaultCenter] addObserver:self 47 | selector:@selector(didApplicationFinishLaunchingNotification:) 48 | name:NSApplicationDidFinishLaunchingNotification 49 | object:nil]; 50 | } 51 | return self; 52 | } 53 | 54 | -(void)didApplicationFinishLaunchingNotification:(NSNotification*)notification{ 55 | 56 | // Sample Menu Item: 57 | NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"]; 58 | 59 | if (editMenuItem) { 60 | [editMenuItem.submenu addItem:[NSMenuItem separatorItem]]; 61 | 62 | multiEditSubmenu = [[NSMenu alloc] init]; 63 | NSMenuItem *multiEditMenuItem = [[NSMenuItem alloc] initWithTitle:@"MultiEdit" action:nil keyEquivalent:@""]; 64 | 65 | [editMenuItem.submenu addItem:multiEditMenuItem]; 66 | [multiEditMenuItem setSubmenu:multiEditSubmenu]; 67 | 68 | // if the user wants a custom keybinding, that might be tricky 69 | NSMenuItem *editNextMenuItem = [[NSMenuItem alloc] initWithTitle:@"Select Next" action:@selector(editNextItem) keyEquivalent:@""]; 70 | [editNextMenuItem setTarget:self]; 71 | [multiEditSubmenu addItem:editNextMenuItem]; 72 | 73 | NSMenuItem *undoLastMenuItem = [[NSMenuItem alloc] initWithTitle:@"Undo Last Selection" action:@selector(undoLastSelection) keyEquivalent:@""]; 74 | [undoLastMenuItem setTarget:self]; 75 | [multiEditSubmenu addItem:undoLastMenuItem]; 76 | 77 | NSMenuItem *skipNextMenuItem = [[NSMenuItem alloc] initWithTitle:@"Skip Last Selection" action:@selector(skipLastSelection) keyEquivalent:@""]; 78 | [skipNextMenuItem setTarget:self]; 79 | [multiEditSubmenu addItem:skipNextMenuItem]; 80 | 81 | // NSMenuItem *settingsMenuItem = [[NSMenuItem alloc] initWithTitle:@"Settings" action:@selector(settingsItem) keyEquivalent:@""]; 82 | // [settingsMenuItem setTarget:self]; 83 | // [multiEditSubmenu addItem:settingsMenuItem]; 84 | 85 | [self setSubmenuKeyEquivalents]; 86 | } 87 | 88 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 89 | } 90 | 91 | -(void)setSubmenuKeyEquivalents{ 92 | [[multiEditSubmenu itemAtIndex:0] setKeyEquivalent:@"a"]; 93 | [[multiEditSubmenu itemAtIndex:0] setKeyEquivalentModifierMask:NSShiftKeyMask | NSAlternateKeyMask]; 94 | 95 | [[multiEditSubmenu itemAtIndex:1] setKeyEquivalent:@"z"]; 96 | [[multiEditSubmenu itemAtIndex:1] setKeyEquivalentModifierMask:NSShiftKeyMask | NSAlternateKeyMask]; 97 | 98 | [[multiEditSubmenu itemAtIndex:2] setKeyEquivalent:@"s"]; 99 | [[multiEditSubmenu itemAtIndex:2] setKeyEquivalentModifierMask:NSShiftKeyMask | NSAlternateKeyMask]; 100 | } 101 | 102 | -(void)editNextItem{ 103 | MultiEditContext *context = [self getCurrentContext]; 104 | if (context) { 105 | [context addNextOccurrence]; 106 | } 107 | } 108 | 109 | -(void)undoLastSelection{ 110 | MultiEditContext *context = [self getCurrentContext]; 111 | if (context) { 112 | [context removeLastOccurrence]; 113 | } 114 | } 115 | 116 | -(void)skipLastSelection{ 117 | MultiEditContext *context = [self getCurrentContext]; 118 | if (context) { 119 | [context skipNextOccurrence]; 120 | } 121 | } 122 | 123 | -(MultiEditContext*)getCurrentContext{ 124 | @try{ // trying to access some private methods if not in a source code editor causes an exception 125 | IDEWorkspaceWindowController *windowController = (IDEWorkspaceWindowController*)[[NSApp keyWindow] windowController]; 126 | IDEEditorArea *editorArea = [windowController editorArea]; 127 | IDEEditorContext *editorAreaContext = [editorArea lastActiveEditorContext]; 128 | IDESourceCodeEditor *editor = [editorAreaContext editor]; 129 | if (![[editor className] isEqualToString:@"IDESourceCodeEditor"]) { 130 | @throw[NSException exceptionWithName:@"Wrong Editor" reason:@"Not inside IDESourceCodeEditor" userInfo:nil]; 131 | } 132 | 133 | MultiEditContext *context = objc_getAssociatedObject(editor, &kMultiEditContextKey); // get context from this editor 134 | 135 | if (!context) { // if no context exists inside this editor 136 | context = [[MultiEditContext alloc] initWithEditor:editor]; 137 | objc_setAssociatedObject(editor, &kMultiEditContextKey, context, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 138 | } 139 | return context; 140 | } 141 | @catch (NSException *exception) { 142 | NSLog(@"%@", exception.reason); 143 | return nil; 144 | } 145 | } 146 | 147 | //-(void)settingsItem{ 148 | // NSWindow *window = [[NSPanel alloc] initWithContentRect:NSRectFromCGRect(CGRectMake(0, 0, 200, 200)) 149 | // styleMask:NSTitledWindowMask 150 | // backing:NSBackingStoreBuffered 151 | // defer:NO]; 152 | // [[NSApp mainWindow] beginSheet:window completionHandler:nil]; 153 | //} 154 | @end 155 | -------------------------------------------------------------------------------- /XcodeMultiEdit/MultiEditContext.h: -------------------------------------------------------------------------------- 1 | // 2 | // MultiEditContext.h 3 | // XcodeMultiEdit 4 | // 5 | // Created by Timothy Edwards on 14/10/2015. 6 | // Copyright © 2015 Tim. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "XCPrivateMethods.h" 12 | 13 | @interface MultiEditContext : NSObject 14 | 15 | -(instancetype)initWithEditor:(IDESourceCodeEditor*)editor; 16 | 17 | -(void)addNextOccurrence; // returns wrapped NSRange of new occurence 18 | -(void)removeLastOccurrence; 19 | -(void)skipNextOccurrence; 20 | 21 | @end -------------------------------------------------------------------------------- /XcodeMultiEdit/MultiEditContext.m: -------------------------------------------------------------------------------- 1 | // 2 | // MultiEditContext.m 3 | // XcodeMultiEdit 4 | // 5 | // Created by Timothy Edwards on 14/10/2015. 6 | // Copyright © 2015 Tim. All rights reserved. 7 | // 8 | 9 | #import "MultiEditContext.h" 10 | 11 | #import "MultiEditView.h" 12 | 13 | @interface MultiEditContext (){ 14 | NSTextView *mainTextView; 15 | IDESourceCodeDocument *mainDocument; 16 | DVTSourceTextStorage *mainTextStorage; 17 | 18 | NSMutableArray *editViewsBeforeSelected; 19 | NSMutableArray *editViewsAfterSelected; 20 | NSString *selectedString; 21 | NSRange selectedRange; 22 | NSView *containerView; 23 | NSTextField *textField; 24 | } 25 | 26 | @end 27 | 28 | @implementation MultiEditContext 29 | 30 | -(instancetype)initWithEditor:(IDESourceCodeEditor *)editor{ 31 | self = [super init]; 32 | if (self) { 33 | mainTextView = [editor textView]; 34 | mainDocument = [editor sourceCodeDocument]; 35 | mainTextStorage = [editor sourceCodeDocument].textStorage; 36 | 37 | editViewsBeforeSelected = [[NSMutableArray alloc] init]; 38 | editViewsAfterSelected = [[NSMutableArray alloc] init]; 39 | } 40 | return self; 41 | } 42 | 43 | -(void)setupFirstOccurence{ 44 | selectedString = [mainTextView.string substringWithRange:mainTextView.selectedRange]; 45 | selectedRange = mainTextView.selectedRange; 46 | [self createTextFieldViewForStringRange:selectedRange]; 47 | [mainTextView setEditable:NO]; 48 | } 49 | 50 | -(void)addNextOccurrence{ 51 | 52 | if ((editViewsBeforeSelected.count==0) && (editViewsAfterSelected.count==0)) { 53 | [self setupFirstOccurence]; 54 | } 55 | 56 | if (![textField.stringValue isEqualToString:selectedString]) { // if started editing text, no more selecting occurences 57 | NSBeep(); 58 | return; 59 | } 60 | 61 | NSRange latestRange; // last range to be added 62 | 63 | BOOL loopedAround = NO; // are we looped to the beginning of the document 64 | 65 | if (editViewsBeforeSelected.count>0) { 66 | latestRange = [[editViewsBeforeSelected lastObject] presentedRange]; 67 | loopedAround = YES; 68 | } else if (editViewsAfterSelected.count>0){ 69 | latestRange = [[editViewsAfterSelected lastObject] presentedRange]; 70 | } else { // havn't got any saved ranges yet 71 | latestRange = selectedRange; 72 | } 73 | 74 | NSUInteger locationOfSearchRange = latestRange.location+latestRange.length; 75 | NSUInteger lengthOfSearchRange; 76 | 77 | if (loopedAround) { // if we're looped around to the beginning of the document 78 | lengthOfSearchRange = selectedRange.location-locationOfSearchRange; 79 | } else { // else we're still on the first read-through 80 | lengthOfSearchRange = mainTextView.string.length - locationOfSearchRange; 81 | } 82 | 83 | NSRange rangeToSearch = NSMakeRange(locationOfSearchRange, lengthOfSearchRange); 84 | 85 | // the search 86 | NSRange newRange = [mainTextView.string rangeOfString:selectedString options:0 range:rangeToSearch]; 87 | 88 | BOOL foundResult = NO; 89 | if (newRange.location != NSNotFound) { // if a range is found, use it. 90 | foundResult = YES; 91 | [self createEditViewForStringRange:newRange]; 92 | } else { 93 | if (!loopedAround) { // so we only loop around once 94 | NSRange rangeLoopedAround = NSMakeRange(0, selectedRange.location); // from beginning of document 95 | newRange = [mainTextView.string rangeOfString:selectedString options:0 range:rangeLoopedAround]; 96 | if (newRange.location != NSNotFound) { // to selected range 97 | foundResult = YES; 98 | [self createEditViewForStringRange:newRange]; 99 | } 100 | } 101 | } 102 | if (!foundResult) { 103 | NSBeep(); 104 | } 105 | } 106 | 107 | -(void)removeLastOccurrence{ 108 | if (editViewsBeforeSelected.count>0){ 109 | [mainTextView scrollRangeToVisible:[editViewsBeforeSelected.lastObject presentedRange]]; 110 | [editViewsBeforeSelected.lastObject removeFromSuperview]; 111 | [editViewsBeforeSelected removeLastObject]; 112 | } else if(editViewsAfterSelected.count>0){ 113 | [mainTextView scrollRangeToVisible:[editViewsAfterSelected.lastObject presentedRange]]; 114 | [editViewsAfterSelected.lastObject removeFromSuperview]; 115 | [editViewsAfterSelected removeLastObject]; 116 | } 117 | if ((editViewsBeforeSelected.count==0) && (editViewsAfterSelected.count==0)) { 118 | [self enterKeyPressed]; // no more views to remove 119 | } 120 | } 121 | 122 | -(void)skipNextOccurrence{ 123 | MultiEditView *viewToSkip; 124 | [self addNextOccurrence]; 125 | if (editViewsBeforeSelected.count>0) { // get last view added 126 | viewToSkip = editViewsBeforeSelected.lastObject; 127 | } else viewToSkip = editViewsAfterSelected.lastObject; 128 | [self addNextOccurrence]; 129 | [editViewsBeforeSelected removeObject:viewToSkip]; 130 | [editViewsAfterSelected removeObject:viewToSkip]; 131 | [viewToSkip removeFromSuperview]; 132 | } 133 | 134 | -(void)createEditViewForStringRange:(NSRange)range{ 135 | // currently using NSViews but perhaps should be editing the NSAttributedString's background color 136 | NSRect rangeRect = [[mainTextView layoutManager] boundingRectForGlyphRange:range inTextContainer:mainTextView.textContainer]; 137 | MultiEditView *editView = [[MultiEditView alloc] initWithFrame:rangeRect]; 138 | [editView setPresentedRange:range]; 139 | [mainTextView addSubview:editView]; 140 | if ((range.location+range.length) 10 | 11 | @interface MultiEditView : NSView 12 | 13 | @property NSRange presentedRange; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /XcodeMultiEdit/MultiEditView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RangeEditView.m 3 | // xcodeplugin 4 | // 5 | // Created by Timothy Edwards on 12/10/2015. 6 | // Copyright © 2015 Tim. All rights reserved. 7 | // 8 | 9 | #import "MultiEditView.h" 10 | 11 | @implementation MultiEditView 12 | 13 | -(instancetype)initWithFrame:(NSRect)frameRect{ 14 | self = [super initWithFrame:frameRect]; 15 | [self setWantsLayer:YES]; 16 | 17 | CGColorRef bgColor = CGColorCreateGenericGray(0.5, 0.3); 18 | [self.layer setBackgroundColor:bgColor]; 19 | CGColorRelease(bgColor); 20 | 21 | CGColorRef borderColor = CGColorCreateGenericGray(0.0, 1.0); 22 | [self.layer setBorderColor:borderColor]; 23 | CGColorRelease(borderColor); 24 | 25 | [self.layer setBorderWidth:1.0]; 26 | [self.layer setCornerRadius:2.0]; 27 | return self; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /XcodeMultiEdit/XCPrivateMethods.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 | -------------------------------------------------------------------------------- /img/one.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwredwards/Xcode-Multi-Edit-Plugin/0f744077ad7520e879ec76c5d58dae51014d9126/img/one.gif -------------------------------------------------------------------------------- /img/static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwredwards/Xcode-Multi-Edit-Plugin/0f744077ad7520e879ec76c5d58dae51014d9126/img/static.png -------------------------------------------------------------------------------- /img/two.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwredwards/Xcode-Multi-Edit-Plugin/0f744077ad7520e879ec76c5d58dae51014d9126/img/two.gif --------------------------------------------------------------------------------