├── .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 | 
16 |
17 |
18 |
19 |
20 |
21 | 
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
--------------------------------------------------------------------------------