├── .gitignore ├── README.md ├── XcodeVimMap.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── brycepauken.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── XcodeVimMap ├── Info.plist ├── XcodeVimMap.h └── XcodeVimMap.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,cocoapods,objective-c,swift,fastlane,ruby,xcode,visualstudiocode,vim 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,cocoapods,objective-c,swift,fastlane,ruby,xcode,visualstudiocode,vim 3 | 4 | ### CocoaPods ### 5 | ## CocoaPods GitIgnore Template 6 | 7 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 8 | # - Also handy if you have a large number of dependant pods 9 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 10 | Pods/ 11 | 12 | ### fastlane ### 13 | # fastlane - A streamlined workflow tool for Cocoa deployment 14 | # 15 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 16 | # screenshots whenever they are needed. 17 | # For more information about the recommended setup visit: 18 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 19 | 20 | # fastlane specific 21 | fastlane/report.xml 22 | 23 | # deliver temporary files 24 | fastlane/Preview.html 25 | 26 | # snapshot generated screenshots 27 | fastlane/screenshots/**/*.png 28 | fastlane/screenshots/screenshots.html 29 | 30 | # scan temporary files 31 | fastlane/test_output 32 | 33 | # Fastlane.swift runner binary 34 | fastlane/FastlaneRunner 35 | 36 | ### macOS ### 37 | # General 38 | .DS_Store 39 | .AppleDouble 40 | .LSOverride 41 | 42 | # Icon must end with two \r 43 | Icon 44 | 45 | # Thumbnails 46 | ._* 47 | 48 | # Files that might appear in the root of a volume 49 | .DocumentRevisions-V100 50 | .fseventsd 51 | .Spotlight-V100 52 | .TemporaryItems 53 | .Trashes 54 | .VolumeIcon.icns 55 | .com.apple.timemachine.donotpresent 56 | 57 | # Directories potentially created on remote AFP share 58 | .AppleDB 59 | .AppleDesktop 60 | Network Trash Folder 61 | Temporary Items 62 | .apdisk 63 | 64 | ### macOS Patch ### 65 | # iCloud generated files 66 | *.icloud 67 | 68 | ### Objective-C ### 69 | # Xcode 70 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 71 | 72 | ## User settings 73 | xcuserdata/ 74 | 75 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 76 | *.xcscmblueprint 77 | *.xccheckout 78 | 79 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 80 | build/ 81 | DerivedData/ 82 | *.moved-aside 83 | *.pbxuser 84 | !default.pbxuser 85 | *.mode1v3 86 | !default.mode1v3 87 | *.mode2v3 88 | !default.mode2v3 89 | *.perspectivev3 90 | !default.perspectivev3 91 | 92 | ## Obj-C/Swift specific 93 | *.hmap 94 | 95 | ## App packaging 96 | *.ipa 97 | *.dSYM.zip 98 | *.dSYM 99 | 100 | # CocoaPods 101 | # We recommend against adding the Pods directory to your .gitignore. However 102 | # you should judge for yourself, the pros and cons are mentioned at: 103 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 104 | # Pods/ 105 | # Add this line if you want to avoid checking in source code from the Xcode workspace 106 | # *.xcworkspace 107 | 108 | # Carthage 109 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 110 | # Carthage/Checkouts 111 | 112 | Carthage/Build/ 113 | 114 | # fastlane 115 | # It is recommended to not store the screenshots in the git repo. 116 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 117 | # For more information about the recommended setup visit: 118 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 119 | 120 | 121 | # Code Injection 122 | # After new code Injection tools there's a generated folder /iOSInjectionProject 123 | # https://github.com/johnno1962/injectionforxcode 124 | 125 | iOSInjectionProject/ 126 | 127 | ### Objective-C Patch ### 128 | 129 | ### Ruby ### 130 | *.gem 131 | *.rbc 132 | /.config 133 | /coverage/ 134 | /InstalledFiles 135 | /pkg/ 136 | /spec/reports/ 137 | /spec/examples.txt 138 | /test/tmp/ 139 | /test/version_tmp/ 140 | /tmp/ 141 | 142 | # Used by dotenv library to load environment variables. 143 | # .env 144 | 145 | # Ignore Byebug command history file. 146 | .byebug_history 147 | 148 | ## Specific to RubyMotion: 149 | .dat* 150 | .repl_history 151 | *.bridgesupport 152 | build-iPhoneOS/ 153 | build-iPhoneSimulator/ 154 | 155 | ## Specific to RubyMotion (use of CocoaPods): 156 | # We recommend against adding the Pods directory to your .gitignore. However 157 | # you should judge for yourself, the pros and cons are mentioned at: 158 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 159 | # vendor/Pods/ 160 | 161 | ## Documentation cache and generated files: 162 | /.yardoc/ 163 | /_yardoc/ 164 | /doc/ 165 | /rdoc/ 166 | 167 | ## Environment normalization: 168 | /.bundle/ 169 | /vendor/bundle 170 | /lib/bundler/man/ 171 | 172 | # for a library or gem, you might want to ignore these files since the code is 173 | # intended to run in multiple environments; otherwise, check them in: 174 | # Gemfile.lock 175 | # .ruby-version 176 | # .ruby-gemset 177 | 178 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 179 | .rvmrc 180 | 181 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 182 | # .rubocop-https?--* 183 | 184 | ### Swift ### 185 | # Xcode 186 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 187 | 188 | 189 | 190 | 191 | 192 | 193 | ## Playgrounds 194 | timeline.xctimeline 195 | playground.xcworkspace 196 | 197 | # Swift Package Manager 198 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 199 | # Packages/ 200 | # Package.pins 201 | # Package.resolved 202 | # *.xcodeproj 203 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 204 | # hence it is not needed unless you have added a package configuration file to your project 205 | # .swiftpm 206 | 207 | .build/ 208 | 209 | # CocoaPods 210 | # We recommend against adding the Pods directory to your .gitignore. However 211 | # you should judge for yourself, the pros and cons are mentioned at: 212 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 213 | # Pods/ 214 | # Add this line if you want to avoid checking in source code from the Xcode workspace 215 | # *.xcworkspace 216 | 217 | # Carthage 218 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 219 | # Carthage/Checkouts 220 | 221 | 222 | # Accio dependency management 223 | Dependencies/ 224 | .accio/ 225 | 226 | # fastlane 227 | # It is recommended to not store the screenshots in the git repo. 228 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 229 | # For more information about the recommended setup visit: 230 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 231 | 232 | 233 | # Code Injection 234 | # After new code Injection tools there's a generated folder /iOSInjectionProject 235 | # https://github.com/johnno1962/injectionforxcode 236 | 237 | 238 | ### Vim ### 239 | # Swap 240 | [._]*.s[a-v][a-z] 241 | !*.svg # comment out if you don't need vector files 242 | [._]*.sw[a-p] 243 | [._]s[a-rt-v][a-z] 244 | [._]ss[a-gi-z] 245 | [._]sw[a-p] 246 | 247 | # Session 248 | Session.vim 249 | Sessionx.vim 250 | 251 | # Temporary 252 | .netrwhist 253 | *~ 254 | # Auto-generated tag files 255 | tags 256 | # Persistent undo 257 | [._]*.un~ 258 | 259 | ### VisualStudioCode ### 260 | .vscode/* 261 | !.vscode/settings.json 262 | !.vscode/tasks.json 263 | !.vscode/launch.json 264 | !.vscode/extensions.json 265 | !.vscode/*.code-snippets 266 | 267 | # Local History for Visual Studio Code 268 | .history/ 269 | 270 | # Built Visual Studio Code Extensions 271 | *.vsix 272 | 273 | ### VisualStudioCode Patch ### 274 | # Ignore all local history of files 275 | .history 276 | .ionide 277 | 278 | # Support for Project snippet scope 279 | .vscode/*.code-snippets 280 | 281 | # Ignore code-workspaces 282 | *.code-workspace 283 | 284 | ### Xcode ### 285 | 286 | ## Xcode 8 and earlier 287 | 288 | ### Xcode Patch ### 289 | *.xcodeproj/* 290 | !*.xcodeproj/project.pbxproj 291 | !*.xcodeproj/xcshareddata/ 292 | !*.xcworkspace/contents.xcworkspacedata 293 | /*.gcno 294 | **/xcshareddata/WorkspaceSettings.xcsettings 295 | 296 | # End of https://www.toptal.com/developers/gitignore/api/macos,cocoapods,objective-c,swift,fastlane,ruby,xcode,visualstudiocode,vim 297 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xcode Vim Map 2 | 3 | Source code for the plugin built in 4 | [this post](https://bryce.co/xcode-vim-map/) on [bryce.co](https://bryce.co/). 5 | 6 | Adds the "jk" key sequence as an option for exiting insert mode in Xcode 13's Vim Mode. 7 | 8 | Tested with Xcode 13.0 – Xcode 16.0b1 . 9 | -------------------------------------------------------------------------------- /XcodeVimMap.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 020463C62B755EB900BF01DB /* XcodeVimMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 020463C42B755EB900BF01DB /* XcodeVimMap.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | 020463B52B755DCA00BF01DB /* XcodeVimMap.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XcodeVimMap.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 15 | 020463C22B755EB900BF01DB /* XcodeVimMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XcodeVimMap.h; sourceTree = ""; }; 16 | 020463C32B755EB900BF01DB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17 | 020463C42B755EB900BF01DB /* XcodeVimMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XcodeVimMap.m; sourceTree = ""; }; 18 | /* End PBXFileReference section */ 19 | 20 | /* Begin PBXFrameworksBuildPhase section */ 21 | 020463B22B755DCA00BF01DB /* Frameworks */ = { 22 | isa = PBXFrameworksBuildPhase; 23 | buildActionMask = 2147483647; 24 | files = ( 25 | ); 26 | runOnlyForDeploymentPostprocessing = 0; 27 | }; 28 | /* End PBXFrameworksBuildPhase section */ 29 | 30 | /* Begin PBXGroup section */ 31 | 020463AC2B755DCA00BF01DB = { 32 | isa = PBXGroup; 33 | children = ( 34 | 020463C12B755E9E00BF01DB /* XcodeVimMap */, 35 | 020463B62B755DCA00BF01DB /* Products */, 36 | ); 37 | sourceTree = ""; 38 | }; 39 | 020463B62B755DCA00BF01DB /* Products */ = { 40 | isa = PBXGroup; 41 | children = ( 42 | 020463B52B755DCA00BF01DB /* XcodeVimMap.xcplugin */, 43 | ); 44 | name = Products; 45 | sourceTree = ""; 46 | }; 47 | 020463C12B755E9E00BF01DB /* XcodeVimMap */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | 020463C32B755EB900BF01DB /* Info.plist */, 51 | 020463C22B755EB900BF01DB /* XcodeVimMap.h */, 52 | 020463C42B755EB900BF01DB /* XcodeVimMap.m */, 53 | ); 54 | path = XcodeVimMap; 55 | sourceTree = ""; 56 | }; 57 | /* End PBXGroup section */ 58 | 59 | /* Begin PBXNativeTarget section */ 60 | 020463B42B755DCA00BF01DB /* XcodeVimMap */ = { 61 | isa = PBXNativeTarget; 62 | buildConfigurationList = 020463B92B755DCA00BF01DB /* Build configuration list for PBXNativeTarget "XcodeVimMap" */; 63 | buildPhases = ( 64 | 020463B12B755DCA00BF01DB /* Sources */, 65 | 020463B22B755DCA00BF01DB /* Frameworks */, 66 | 020463B32B755DCA00BF01DB /* Resources */, 67 | ); 68 | buildRules = ( 69 | ); 70 | dependencies = ( 71 | ); 72 | name = XcodeVimMap; 73 | productName = XcodeVimMap; 74 | productReference = 020463B52B755DCA00BF01DB /* XcodeVimMap.xcplugin */; 75 | productType = "com.apple.product-type.bundle"; 76 | }; 77 | /* End PBXNativeTarget section */ 78 | 79 | /* Begin PBXProject section */ 80 | 020463AD2B755DCA00BF01DB /* Project object */ = { 81 | isa = PBXProject; 82 | attributes = { 83 | BuildIndependentTargetsInParallel = 1; 84 | LastUpgradeCheck = 1520; 85 | TargetAttributes = { 86 | 020463B42B755DCA00BF01DB = { 87 | CreatedOnToolsVersion = 15.2; 88 | }; 89 | }; 90 | }; 91 | buildConfigurationList = 020463B02B755DCA00BF01DB /* Build configuration list for PBXProject "XcodeVimMap" */; 92 | compatibilityVersion = "Xcode 14.0"; 93 | developmentRegion = en; 94 | hasScannedForEncodings = 0; 95 | knownRegions = ( 96 | en, 97 | Base, 98 | ); 99 | mainGroup = 020463AC2B755DCA00BF01DB; 100 | productRefGroup = 020463B62B755DCA00BF01DB /* Products */; 101 | projectDirPath = ""; 102 | projectRoot = ""; 103 | targets = ( 104 | 020463B42B755DCA00BF01DB /* XcodeVimMap */, 105 | ); 106 | }; 107 | /* End PBXProject section */ 108 | 109 | /* Begin PBXResourcesBuildPhase section */ 110 | 020463B32B755DCA00BF01DB /* Resources */ = { 111 | isa = PBXResourcesBuildPhase; 112 | buildActionMask = 2147483647; 113 | files = ( 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | /* End PBXResourcesBuildPhase section */ 118 | 119 | /* Begin PBXSourcesBuildPhase section */ 120 | 020463B12B755DCA00BF01DB /* Sources */ = { 121 | isa = PBXSourcesBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | 020463C62B755EB900BF01DB /* XcodeVimMap.m in Sources */, 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | /* End PBXSourcesBuildPhase section */ 129 | 130 | /* Begin XCBuildConfiguration section */ 131 | 020463B72B755DCA00BF01DB /* Debug */ = { 132 | isa = XCBuildConfiguration; 133 | buildSettings = { 134 | ALWAYS_SEARCH_USER_PATHS = NO; 135 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 136 | CLANG_ANALYZER_NONNULL = YES; 137 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 138 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 139 | CLANG_ENABLE_MODULES = YES; 140 | CLANG_ENABLE_OBJC_ARC = YES; 141 | CLANG_ENABLE_OBJC_WEAK = YES; 142 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 143 | CLANG_WARN_BOOL_CONVERSION = YES; 144 | CLANG_WARN_COMMA = YES; 145 | CLANG_WARN_CONSTANT_CONVERSION = YES; 146 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 147 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 148 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 149 | CLANG_WARN_EMPTY_BODY = YES; 150 | CLANG_WARN_ENUM_CONVERSION = YES; 151 | CLANG_WARN_INFINITE_RECURSION = YES; 152 | CLANG_WARN_INT_CONVERSION = YES; 153 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 154 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 155 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 156 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 157 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 158 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 159 | CLANG_WARN_STRICT_PROTOTYPES = YES; 160 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 161 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 162 | CLANG_WARN_UNREACHABLE_CODE = YES; 163 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 164 | COPY_PHASE_STRIP = NO; 165 | DEBUG_INFORMATION_FORMAT = dwarf; 166 | ENABLE_STRICT_OBJC_MSGSEND = YES; 167 | ENABLE_TESTABILITY = YES; 168 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 169 | GCC_C_LANGUAGE_STANDARD = gnu17; 170 | GCC_DYNAMIC_NO_PIC = NO; 171 | GCC_NO_COMMON_BLOCKS = YES; 172 | GCC_OPTIMIZATION_LEVEL = 0; 173 | GCC_PREPROCESSOR_DEFINITIONS = ( 174 | "DEBUG=1", 175 | "$(inherited)", 176 | ); 177 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 178 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 179 | GCC_WARN_UNDECLARED_SELECTOR = YES; 180 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 181 | GCC_WARN_UNUSED_FUNCTION = YES; 182 | GCC_WARN_UNUSED_VARIABLE = YES; 183 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 184 | MACOSX_DEPLOYMENT_TARGET = 14.0; 185 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 186 | MTL_FAST_MATH = YES; 187 | ONLY_ACTIVE_ARCH = YES; 188 | SDKROOT = macosx; 189 | }; 190 | name = Debug; 191 | }; 192 | 020463B82B755DCA00BF01DB /* Release */ = { 193 | isa = XCBuildConfiguration; 194 | buildSettings = { 195 | ALWAYS_SEARCH_USER_PATHS = NO; 196 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 197 | CLANG_ANALYZER_NONNULL = YES; 198 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 199 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 200 | CLANG_ENABLE_MODULES = YES; 201 | CLANG_ENABLE_OBJC_ARC = YES; 202 | CLANG_ENABLE_OBJC_WEAK = YES; 203 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 204 | CLANG_WARN_BOOL_CONVERSION = YES; 205 | CLANG_WARN_COMMA = YES; 206 | CLANG_WARN_CONSTANT_CONVERSION = YES; 207 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 208 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 209 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 210 | CLANG_WARN_EMPTY_BODY = YES; 211 | CLANG_WARN_ENUM_CONVERSION = YES; 212 | CLANG_WARN_INFINITE_RECURSION = YES; 213 | CLANG_WARN_INT_CONVERSION = YES; 214 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 215 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 216 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 218 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 219 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 220 | CLANG_WARN_STRICT_PROTOTYPES = YES; 221 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 222 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 223 | CLANG_WARN_UNREACHABLE_CODE = YES; 224 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 225 | COPY_PHASE_STRIP = NO; 226 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 227 | ENABLE_NS_ASSERTIONS = NO; 228 | ENABLE_STRICT_OBJC_MSGSEND = YES; 229 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 230 | GCC_C_LANGUAGE_STANDARD = gnu17; 231 | GCC_NO_COMMON_BLOCKS = YES; 232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 234 | GCC_WARN_UNDECLARED_SELECTOR = YES; 235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 236 | GCC_WARN_UNUSED_FUNCTION = YES; 237 | GCC_WARN_UNUSED_VARIABLE = YES; 238 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 239 | MACOSX_DEPLOYMENT_TARGET = 14.0; 240 | MTL_ENABLE_DEBUG_INFO = NO; 241 | MTL_FAST_MATH = YES; 242 | SDKROOT = macosx; 243 | }; 244 | name = Release; 245 | }; 246 | 020463BA2B755DCA00BF01DB /* Debug */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 250 | CODE_SIGN_STYLE = Automatic; 251 | COMBINE_HIDPI_IMAGES = YES; 252 | CURRENT_PROJECT_VERSION = 1; 253 | DEVELOPMENT_TEAM = FGFSR7EX83; 254 | GENERATE_INFOPLIST_FILE = NO; 255 | INFOPLIST_FILE = XcodeVimMap/Info.plist; 256 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 257 | INFOPLIST_KEY_NSPrincipalClass = ""; 258 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 259 | MARKETING_VERSION = 1.0; 260 | PRODUCT_BUNDLE_IDENTIFIER = com.brycepauken.XcodeVimMap; 261 | PRODUCT_NAME = "$(TARGET_NAME)"; 262 | SKIP_INSTALL = NO; 263 | SWIFT_EMIT_LOC_STRINGS = YES; 264 | WRAPPER_EXTENSION = xcplugin; 265 | }; 266 | name = Debug; 267 | }; 268 | 020463BB2B755DCA00BF01DB /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | CODE_SIGN_IDENTITY = "Developer ID Application"; 272 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; 273 | CODE_SIGN_STYLE = Manual; 274 | COMBINE_HIDPI_IMAGES = YES; 275 | CURRENT_PROJECT_VERSION = 1; 276 | DEVELOPMENT_TEAM = ""; 277 | "DEVELOPMENT_TEAM[sdk=macosx*]" = FGFSR7EX83; 278 | GENERATE_INFOPLIST_FILE = NO; 279 | INFOPLIST_FILE = XcodeVimMap/Info.plist; 280 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 281 | INFOPLIST_KEY_NSPrincipalClass = ""; 282 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 283 | MARKETING_VERSION = 1.0; 284 | OTHER_CODE_SIGN_FLAGS = "--timestamp"; 285 | PRODUCT_BUNDLE_IDENTIFIER = com.brycepauken.XcodeVimMap; 286 | PRODUCT_NAME = "$(TARGET_NAME)"; 287 | PROVISIONING_PROFILE_SPECIFIER = ""; 288 | SKIP_INSTALL = NO; 289 | SWIFT_EMIT_LOC_STRINGS = YES; 290 | WRAPPER_EXTENSION = xcplugin; 291 | }; 292 | name = Release; 293 | }; 294 | /* End XCBuildConfiguration section */ 295 | 296 | /* Begin XCConfigurationList section */ 297 | 020463B02B755DCA00BF01DB /* Build configuration list for PBXProject "XcodeVimMap" */ = { 298 | isa = XCConfigurationList; 299 | buildConfigurations = ( 300 | 020463B72B755DCA00BF01DB /* Debug */, 301 | 020463B82B755DCA00BF01DB /* Release */, 302 | ); 303 | defaultConfigurationIsVisible = 0; 304 | defaultConfigurationName = Release; 305 | }; 306 | 020463B92B755DCA00BF01DB /* Build configuration list for PBXNativeTarget "XcodeVimMap" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | 020463BA2B755DCA00BF01DB /* Debug */, 310 | 020463BB2B755DCA00BF01DB /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | /* End XCConfigurationList section */ 316 | }; 317 | rootObject = 020463AD2B755DCA00BF01DB /* Project object */; 318 | } 319 | -------------------------------------------------------------------------------- /XcodeVimMap.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XcodeVimMap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /XcodeVimMap.xcodeproj/xcuserdata/brycepauken.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | XcodeVimMap.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /XcodeVimMap/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | XC4Compatible 24 | 25 | XCPluginHasUI 26 | 27 | CompatibleProductBuildVersions 28 | 29 | 15E5178i 30 | 15E5188j 31 | 15E5194e 32 | 15E5202a 33 | 15E204a 34 | 15F31d 35 | 16A5171c 36 | 37 | DVTPlugInCompatibilityUUIDs 38 | 39 | EFD92DF8-D0A2-4C92-B6E3-9B3CD7E8DC19 40 | 8BAA96B4-5225-471B-B124-D32A349B8106 41 | 7A3A18B7-4C08-46F0-A96A-AB686D315DF0 42 | BD431FF2-C78B-4953-A3A9-0E42593C2D32 43 | 42E1F17B-27B3-4DE8-92A8-DC76BA4F5921 44 | 3728DE4A-3870-476F-BAE5-216F52C5710A 45 | AF3613FA-81D9-4A8B-8204-9912665677FA 46 | B8A1C62D-289E-476A-B0CD-B16ADBDD8395 47 | C91F3560-00E7-4749-8E3F-4D83B1496051 48 | EA3EACC9-A0BE-423D-9BA3-AA8B9FE68E38 49 | EB1EF21B-E756-4D3D-A6EA-E9C57D8C1924 50 | EB2858C6-D4A9-4096-9AA3-BB5872AE7EF9 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /XcodeVimMap/XcodeVimMap.h: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeVimMap.h 3 | // XcodeVimMap 4 | // 5 | // Created by Bryce Pauken on 8/1/21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface XcodeVimMap : NSObject 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /XcodeVimMap/XcodeVimMap.m: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeVimMap.m 3 | // XcodeVimMap 4 | // 5 | // Created by Bryce Pauken on 8/1/21. 6 | // 7 | 8 | #import "XcodeVimMap.h" 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | typedef NS_ENUM(uint8_t, VimMode) { 15 | VimModeInsert = 5 16 | }; 17 | 18 | @implementation XcodeVimMap 19 | 20 | // MARK: - Plugin Startup & Swizzling Setup 21 | 22 | static BOOL(*originalKeyDown)(id self, SEL _cmd, NSEvent *event); 23 | 24 | + (void)pluginDidLoad:(NSBundle *)plugin { 25 | NSLog(@"[XcodeVimMap] Plugin Loaded"); 26 | 27 | static dispatch_once_t token = 0; 28 | dispatch_once(&token, ^{ 29 | [self swizzleKeyDown]; 30 | }); 31 | } 32 | 33 | + (void)swizzleKeyDown { 34 | NSString *xcodePath = [[NSBundle mainBundle] bundlePath]; 35 | NSString *sourceEditorPath = [xcodePath stringByAppendingPathComponent:@"Contents/SharedFrameworks/SourceEditor.framework/Versions/A/SourceEditor"]; 36 | dlopen([sourceEditorPath cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW); 37 | 38 | NSLog(@"[XcodeVimMap] SourceEditor Loaded"); 39 | 40 | Method originalMethod = class_getInstanceMethod( 41 | NSClassFromString(@"SourceEditor.SourceEditorView"), 42 | NSSelectorFromString(@"keyDown:")); 43 | 44 | Method replacementMethod = class_getInstanceMethod( 45 | [self class], 46 | @selector(swizzled_keyDown:)); 47 | 48 | originalKeyDown = (void *)method_getImplementation(originalMethod); 49 | method_setImplementation( 50 | originalMethod, 51 | method_getImplementation(replacementMethod)); 52 | } 53 | 54 | // MARK: - Swizzle Implementation 55 | 56 | static NSEvent *queuedEvent; 57 | static dispatch_block_t sendQueuedEventAfterTimeout; 58 | 59 | - (BOOL)swizzled_keyDown:(NSEvent *)event { 60 | // Block to send an event to the original implementation 61 | BOOL (^sendEvent)(NSEvent *) = ^BOOL(NSEvent *event) { 62 | return originalKeyDown(self, _cmd, event); 63 | }; 64 | 65 | // Get the current vim mode 66 | VimMode vimMode = [XcodeVimMap vimModeFromSourceEditorView:self]; 67 | 68 | // Exit early if we're not in insert mode 69 | if (vimMode != VimModeInsert) { 70 | return sendEvent(event); 71 | } 72 | 73 | // Check if we've recieved a `k` press while a `j` press is still queued 74 | if ([event.characters isEqualToString:@"k"] && queuedEvent != nil) { 75 | // Clear the queued j press 76 | queuedEvent = nil; 77 | 78 | // And cancel its timeout-based-sending. 79 | dispatch_block_cancel(sendQueuedEventAfterTimeout); 80 | 81 | // Create an "escape" key event and send it, 82 | // returning early in the process. 83 | NSEvent *escapeEvent = [XcodeVimMap modifiedEvent:event withCharacters:@"\x1b"]; 84 | return sendEvent(escapeEvent); 85 | } 86 | 87 | // Check if we have a previous `j` press queued up 88 | if (queuedEvent != nil) { 89 | // Apply the `j` press 90 | sendEvent(queuedEvent); 91 | 92 | // Clear the queued event so it's not sent again 93 | queuedEvent = nil; 94 | 95 | // Cancel the timeout-based application of the event, 96 | // since we've invoked it manually. 97 | dispatch_block_cancel(sendQueuedEventAfterTimeout); 98 | } 99 | 100 | // Check if we've recieved a "j" keypress 101 | if ([event.characters isEqualToString:@"j"]) { 102 | // Save a reference to the event for later sending 103 | queuedEvent = event; 104 | 105 | // Create a block to send the event for use in the timeout functionality 106 | sendQueuedEventAfterTimeout = dispatch_block_create(0, ^{ 107 | // Send the queued event 108 | sendEvent(queuedEvent); 109 | 110 | // Clear out the queued event so that it won't be sent again 111 | queuedEvent = nil; 112 | }); 113 | 114 | // Invoke the above block after 1 second. This invocation 115 | // should be cancelled if the event is manually sendt sooner. 116 | dispatch_after( 117 | dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), 118 | dispatch_get_main_queue(), sendQueuedEventAfterTimeout); 119 | 120 | // Return early so that the default implementation 121 | // (which would apply our `j` press immediately) 122 | // is not called. 123 | return true; 124 | } 125 | 126 | // Fall back to default implementation 127 | return sendEvent(event); 128 | } 129 | 130 | // MARK: - Helpers 131 | 132 | + (NSEvent *)modifiedEvent:(NSEvent *)event withCharacters:(NSString *)characters { 133 | return [NSEvent keyEventWithType:event.type 134 | location:event.locationInWindow 135 | modifierFlags:event.modifierFlags 136 | timestamp:event.timestamp 137 | windowNumber:event.windowNumber 138 | context:nil // event.context is deprecated and only returns `nil` 139 | characters:characters 140 | charactersIgnoringModifiers:characters // unclear if we have to worry about this distiction 141 | isARepeat:NO 142 | keyCode:event.keyCode]; 143 | } 144 | 145 | + (id)getIvar:(NSString *)ivarName from:(NSObject *)object { 146 | const char *ivarNameCString = [ivarName cStringUsingEncoding:NSUTF8StringEncoding]; 147 | Ivar ivar = class_getInstanceVariable([object class], ivarNameCString); 148 | return object_getIvar(object, ivar); 149 | } 150 | 151 | + (uint8_t)getIntIvar:(NSString *)ivarName from:(NSObject *)object { 152 | const char *ivarNameCString = [ivarName cStringUsingEncoding:NSUTF8StringEncoding]; 153 | Ivar ivar = class_getInstanceVariable([object class], ivarNameCString); 154 | // In Apple Silicon, the compiler will try to retain the return value from `object_getIvar` then crash. 155 | // In this case, we cast the `object_getIvar` to avoid this. 156 | uint8_t result = ((uint8_t (*)(id, Ivar))object_getIvar)(object, ivar); 157 | return result; 158 | } 159 | 160 | + (NSArray *)arrayFromSwiftArrayStorage:(void *)swiftArrayStorage { 161 | // Create a mutable array to hold each encountered element 162 | NSMutableArray *results = [NSMutableArray new]; 163 | 164 | // Read array length at offset 0x10 165 | long arrayLength = *(long *)((char *)swiftArrayStorage + 0x10); 166 | 167 | // Get each element of the array, every 0x10 bytes, starting at offset 0x20 168 | for (long i=0; i