├── .DS_Store ├── MLAutoReplace.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── MLAutoReplace.xccheckout │ └── xcuserdata │ │ └── molon.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── MLAutoReplace.xcscheme └── xcuserdata │ └── molon.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── MLAutoReplace ├── Category │ ├── NSDate+Addition.h │ ├── NSDate+Addition.m │ ├── NSString+Addition.h │ ├── NSString+Addition.m │ ├── NSString+PDRegex.h │ ├── NSString+PDRegex.m │ ├── NSTextView+Addition.h │ └── NSTextView+Addition.m ├── Debug.h ├── DefaultReplaceGetter.plist ├── DefaultReplaceOther.plist ├── Info.plist ├── KeyboardHelper │ ├── MLKeyboardEventSender.h │ └── MLKeyboardEventSender.m ├── MLAutoReplace.h ├── MLAutoReplace.m └── SettingWindowController │ ├── SettingWindowController.h │ ├── SettingWindowController.m │ └── SettingWindowController.xib ├── README.md ├── addReplaceGetter.gif ├── pseudo-generic.gif ├── re-intent-setting.png ├── regex.png ├── replaceGetter.gif ├── replaceOther.gif └── replaceTS.gif /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/.DS_Store -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 367DEA141A20F39C00A65D14 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 367DEA131A20F39C00A65D14 /* AppKit.framework */; }; 11 | 367DEA161A20F39C00A65D14 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 367DEA151A20F39C00A65D14 /* Foundation.framework */; }; 12 | 367DEA1B1A20F39C00A65D14 /* MLAutoReplace.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = 367DEA1A1A20F39C00A65D14 /* MLAutoReplace.xcscheme */; }; 13 | 367DEA431A20F3B800A65D14 /* NSString+Addition.m in Sources */ = {isa = PBXBuildFile; fileRef = 367DEA261A20F3B800A65D14 /* NSString+Addition.m */; }; 14 | 367DEA441A20F3B800A65D14 /* NSString+PDRegex.m in Sources */ = {isa = PBXBuildFile; fileRef = 367DEA281A20F3B800A65D14 /* NSString+PDRegex.m */; }; 15 | 367DEA451A20F3B800A65D14 /* NSTextView+Addition.m in Sources */ = {isa = PBXBuildFile; fileRef = 367DEA2A1A20F3B800A65D14 /* NSTextView+Addition.m */; }; 16 | 367DEA471A20F3B800A65D14 /* DefaultReplaceGetter.plist in Resources */ = {isa = PBXBuildFile; fileRef = 367DEA2E1A20F3B800A65D14 /* DefaultReplaceGetter.plist */; }; 17 | 367DEA491A20F3B800A65D14 /* MLKeyboardEventSender.m in Sources */ = {isa = PBXBuildFile; fileRef = 367DEA321A20F3B800A65D14 /* MLKeyboardEventSender.m */; }; 18 | 367DEA4A1A20F3B800A65D14 /* MLAutoReplace.m in Sources */ = {isa = PBXBuildFile; fileRef = 367DEA341A20F3B800A65D14 /* MLAutoReplace.m */; }; 19 | 367DEA4B1A20F3B800A65D14 /* SettingWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 367DEA371A20F3B800A65D14 /* SettingWindowController.m */; }; 20 | 367DEA4C1A20F3B800A65D14 /* SettingWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 367DEA381A20F3B800A65D14 /* SettingWindowController.xib */; }; 21 | 36C56E241AF8601B003498D4 /* NSDate+Addition.m in Sources */ = {isa = PBXBuildFile; fileRef = 36C56E231AF8601B003498D4 /* NSDate+Addition.m */; }; 22 | 36DF18B41CF8AF170073B13A /* DefaultReplaceOther.plist in Resources */ = {isa = PBXBuildFile; fileRef = 36DF18B31CF8AF170073B13A /* DefaultReplaceOther.plist */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 367DEA101A20F39C00A65D14 /* MLAutoReplace.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MLAutoReplace.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 367DEA131A20F39C00A65D14 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 28 | 367DEA151A20F39C00A65D14 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 29 | 367DEA191A20F39C00A65D14 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 367DEA1A1A20F39C00A65D14 /* MLAutoReplace.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = MLAutoReplace.xcscheme; path = MLAutoReplace.xcodeproj/xcshareddata/xcschemes/MLAutoReplace.xcscheme; sourceTree = SOURCE_ROOT; }; 31 | 367DEA251A20F3B800A65D14 /* NSString+Addition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Addition.h"; sourceTree = ""; }; 32 | 367DEA261A20F3B800A65D14 /* NSString+Addition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Addition.m"; sourceTree = ""; }; 33 | 367DEA271A20F3B800A65D14 /* NSString+PDRegex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PDRegex.h"; sourceTree = ""; }; 34 | 367DEA281A20F3B800A65D14 /* NSString+PDRegex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+PDRegex.m"; sourceTree = ""; }; 35 | 367DEA291A20F3B800A65D14 /* NSTextView+Addition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTextView+Addition.h"; sourceTree = ""; }; 36 | 367DEA2A1A20F3B800A65D14 /* NSTextView+Addition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTextView+Addition.m"; sourceTree = ""; }; 37 | 367DEA2D1A20F3B800A65D14 /* Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Debug.h; sourceTree = ""; }; 38 | 367DEA2E1A20F3B800A65D14 /* DefaultReplaceGetter.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DefaultReplaceGetter.plist; sourceTree = ""; }; 39 | 367DEA311A20F3B800A65D14 /* MLKeyboardEventSender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLKeyboardEventSender.h; sourceTree = ""; }; 40 | 367DEA321A20F3B800A65D14 /* MLKeyboardEventSender.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLKeyboardEventSender.m; sourceTree = ""; }; 41 | 367DEA331A20F3B800A65D14 /* MLAutoReplace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLAutoReplace.h; sourceTree = ""; }; 42 | 367DEA341A20F3B800A65D14 /* MLAutoReplace.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLAutoReplace.m; sourceTree = ""; }; 43 | 367DEA361A20F3B800A65D14 /* SettingWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingWindowController.h; sourceTree = ""; }; 44 | 367DEA371A20F3B800A65D14 /* SettingWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingWindowController.m; sourceTree = ""; }; 45 | 367DEA381A20F3B800A65D14 /* SettingWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingWindowController.xib; sourceTree = ""; }; 46 | 36C56E221AF8601B003498D4 /* NSDate+Addition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Addition.h"; sourceTree = ""; }; 47 | 36C56E231AF8601B003498D4 /* NSDate+Addition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Addition.m"; sourceTree = ""; }; 48 | 36DF18B31CF8AF170073B13A /* DefaultReplaceOther.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DefaultReplaceOther.plist; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 367DEA0E1A20F39C00A65D14 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | 367DEA141A20F39C00A65D14 /* AppKit.framework in Frameworks */, 57 | 367DEA161A20F39C00A65D14 /* Foundation.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 367DEA071A20F39C00A65D14 = { 65 | isa = PBXGroup; 66 | children = ( 67 | 367DEA171A20F39C00A65D14 /* MLAutoReplace */, 68 | 367DEA121A20F39C00A65D14 /* Frameworks */, 69 | 367DEA111A20F39C00A65D14 /* Products */, 70 | ); 71 | sourceTree = ""; 72 | }; 73 | 367DEA111A20F39C00A65D14 /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 367DEA101A20F39C00A65D14 /* MLAutoReplace.xcplugin */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | 367DEA121A20F39C00A65D14 /* Frameworks */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 367DEA131A20F39C00A65D14 /* AppKit.framework */, 85 | 367DEA151A20F39C00A65D14 /* Foundation.framework */, 86 | ); 87 | name = Frameworks; 88 | sourceTree = ""; 89 | }; 90 | 367DEA171A20F39C00A65D14 /* MLAutoReplace */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 367DEA241A20F3B800A65D14 /* Category */, 94 | 367DEA351A20F3B800A65D14 /* SettingWindowController */, 95 | 367DEA301A20F3B800A65D14 /* KeyboardHelper */, 96 | 367DEA2D1A20F3B800A65D14 /* Debug.h */, 97 | 36DF18B31CF8AF170073B13A /* DefaultReplaceOther.plist */, 98 | 367DEA2E1A20F3B800A65D14 /* DefaultReplaceGetter.plist */, 99 | 367DEA331A20F3B800A65D14 /* MLAutoReplace.h */, 100 | 367DEA341A20F3B800A65D14 /* MLAutoReplace.m */, 101 | 367DEA181A20F39C00A65D14 /* Supporting Files */, 102 | ); 103 | path = MLAutoReplace; 104 | sourceTree = ""; 105 | }; 106 | 367DEA181A20F39C00A65D14 /* Supporting Files */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 367DEA191A20F39C00A65D14 /* Info.plist */, 110 | 367DEA1A1A20F39C00A65D14 /* MLAutoReplace.xcscheme */, 111 | ); 112 | name = "Supporting Files"; 113 | sourceTree = ""; 114 | }; 115 | 367DEA241A20F3B800A65D14 /* Category */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 367DEA251A20F3B800A65D14 /* NSString+Addition.h */, 119 | 367DEA261A20F3B800A65D14 /* NSString+Addition.m */, 120 | 367DEA271A20F3B800A65D14 /* NSString+PDRegex.h */, 121 | 367DEA281A20F3B800A65D14 /* NSString+PDRegex.m */, 122 | 367DEA291A20F3B800A65D14 /* NSTextView+Addition.h */, 123 | 367DEA2A1A20F3B800A65D14 /* NSTextView+Addition.m */, 124 | 36C56E221AF8601B003498D4 /* NSDate+Addition.h */, 125 | 36C56E231AF8601B003498D4 /* NSDate+Addition.m */, 126 | ); 127 | path = Category; 128 | sourceTree = ""; 129 | }; 130 | 367DEA301A20F3B800A65D14 /* KeyboardHelper */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 367DEA311A20F3B800A65D14 /* MLKeyboardEventSender.h */, 134 | 367DEA321A20F3B800A65D14 /* MLKeyboardEventSender.m */, 135 | ); 136 | path = KeyboardHelper; 137 | sourceTree = ""; 138 | }; 139 | 367DEA351A20F3B800A65D14 /* SettingWindowController */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 367DEA361A20F3B800A65D14 /* SettingWindowController.h */, 143 | 367DEA371A20F3B800A65D14 /* SettingWindowController.m */, 144 | 367DEA381A20F3B800A65D14 /* SettingWindowController.xib */, 145 | ); 146 | path = SettingWindowController; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | 367DEA0F1A20F39C00A65D14 /* MLAutoReplace */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = 367DEA211A20F39C00A65D14 /* Build configuration list for PBXNativeTarget "MLAutoReplace" */; 155 | buildPhases = ( 156 | 367DEA0C1A20F39C00A65D14 /* Sources */, 157 | 367DEA0D1A20F39C00A65D14 /* Resources */, 158 | 367DEA0E1A20F39C00A65D14 /* Frameworks */, 159 | 36480E341BB3AA2900143F13 /* ShellScript */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | ); 165 | name = MLAutoReplace; 166 | productName = MLAutoReplace; 167 | productReference = 367DEA101A20F39C00A65D14 /* MLAutoReplace.xcplugin */; 168 | productType = "com.apple.product-type.bundle"; 169 | }; 170 | /* End PBXNativeTarget section */ 171 | 172 | /* Begin PBXProject section */ 173 | 367DEA081A20F39C00A65D14 /* Project object */ = { 174 | isa = PBXProject; 175 | attributes = { 176 | LastUpgradeCheck = 0610; 177 | ORGANIZATIONNAME = molon; 178 | TargetAttributes = { 179 | 367DEA0F1A20F39C00A65D14 = { 180 | CreatedOnToolsVersion = 6.1; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = 367DEA0B1A20F39C00A65D14 /* Build configuration list for PBXProject "MLAutoReplace" */; 185 | compatibilityVersion = "Xcode 3.2"; 186 | developmentRegion = English; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | en, 190 | ); 191 | mainGroup = 367DEA071A20F39C00A65D14; 192 | productRefGroup = 367DEA111A20F39C00A65D14 /* Products */; 193 | projectDirPath = ""; 194 | projectRoot = ""; 195 | targets = ( 196 | 367DEA0F1A20F39C00A65D14 /* MLAutoReplace */, 197 | ); 198 | }; 199 | /* End PBXProject section */ 200 | 201 | /* Begin PBXResourcesBuildPhase section */ 202 | 367DEA0D1A20F39C00A65D14 /* Resources */ = { 203 | isa = PBXResourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | 367DEA471A20F3B800A65D14 /* DefaultReplaceGetter.plist in Resources */, 207 | 367DEA4C1A20F3B800A65D14 /* SettingWindowController.xib in Resources */, 208 | 36DF18B41CF8AF170073B13A /* DefaultReplaceOther.plist in Resources */, 209 | 367DEA1B1A20F39C00A65D14 /* MLAutoReplace.xcscheme in Resources */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXResourcesBuildPhase section */ 214 | 215 | /* Begin PBXShellScriptBuildPhase section */ 216 | 36480E341BB3AA2900143F13 /* ShellScript */ = { 217 | isa = PBXShellScriptBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | ); 221 | inputPaths = ( 222 | ); 223 | outputPaths = ( 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | shellPath = /bin/sh; 227 | shellScript = "find ~/Library/Application\\ Support/Developer/Shared/Xcode/Plug-ins/MLAutoReplace.xcplugin -name Info.plist -maxdepth 3 | xargs -I{} defaults write {} DVTPlugInCompatibilityUUIDs -array-add `defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID`"; 228 | }; 229 | /* End PBXShellScriptBuildPhase section */ 230 | 231 | /* Begin PBXSourcesBuildPhase section */ 232 | 367DEA0C1A20F39C00A65D14 /* Sources */ = { 233 | isa = PBXSourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | 367DEA4A1A20F3B800A65D14 /* MLAutoReplace.m in Sources */, 237 | 367DEA431A20F3B800A65D14 /* NSString+Addition.m in Sources */, 238 | 367DEA4B1A20F3B800A65D14 /* SettingWindowController.m in Sources */, 239 | 367DEA451A20F3B800A65D14 /* NSTextView+Addition.m in Sources */, 240 | 36C56E241AF8601B003498D4 /* NSDate+Addition.m in Sources */, 241 | 367DEA441A20F3B800A65D14 /* NSString+PDRegex.m in Sources */, 242 | 367DEA491A20F3B800A65D14 /* MLKeyboardEventSender.m in Sources */, 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | /* End PBXSourcesBuildPhase section */ 247 | 248 | /* Begin XCBuildConfiguration section */ 249 | 367DEA1F1A20F39C00A65D14 /* Debug */ = { 250 | isa = XCBuildConfiguration; 251 | buildSettings = { 252 | ALWAYS_SEARCH_USER_PATHS = NO; 253 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 254 | CLANG_CXX_LIBRARY = "libc++"; 255 | CLANG_ENABLE_MODULES = YES; 256 | CLANG_ENABLE_OBJC_ARC = YES; 257 | CLANG_WARN_BOOL_CONVERSION = YES; 258 | CLANG_WARN_CONSTANT_CONVERSION = YES; 259 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 260 | CLANG_WARN_EMPTY_BODY = YES; 261 | CLANG_WARN_ENUM_CONVERSION = YES; 262 | CLANG_WARN_INT_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_UNREACHABLE_CODE = YES; 265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 266 | COPY_PHASE_STRIP = NO; 267 | ENABLE_STRICT_OBJC_MSGSEND = YES; 268 | GCC_C_LANGUAGE_STANDARD = gnu99; 269 | GCC_DYNAMIC_NO_PIC = NO; 270 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 271 | GCC_OPTIMIZATION_LEVEL = 0; 272 | GCC_PREPROCESSOR_DEFINITIONS = ( 273 | "DEBUG=1", 274 | "$(inherited)", 275 | ); 276 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 279 | GCC_WARN_UNDECLARED_SELECTOR = YES; 280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 281 | GCC_WARN_UNUSED_FUNCTION = YES; 282 | GCC_WARN_UNUSED_VARIABLE = YES; 283 | MACOSX_DEPLOYMENT_TARGET = 10.10; 284 | MTL_ENABLE_DEBUG_INFO = YES; 285 | ONLY_ACTIVE_ARCH = YES; 286 | SDKROOT = macosx; 287 | }; 288 | name = Debug; 289 | }; 290 | 367DEA201A20F39C00A65D14 /* Release */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | ALWAYS_SEARCH_USER_PATHS = NO; 294 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 295 | CLANG_CXX_LIBRARY = "libc++"; 296 | CLANG_ENABLE_MODULES = YES; 297 | CLANG_ENABLE_OBJC_ARC = YES; 298 | CLANG_WARN_BOOL_CONVERSION = YES; 299 | CLANG_WARN_CONSTANT_CONVERSION = YES; 300 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 301 | CLANG_WARN_EMPTY_BODY = YES; 302 | CLANG_WARN_ENUM_CONVERSION = YES; 303 | CLANG_WARN_INT_CONVERSION = YES; 304 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 305 | CLANG_WARN_UNREACHABLE_CODE = YES; 306 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 307 | COPY_PHASE_STRIP = YES; 308 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 309 | ENABLE_NS_ASSERTIONS = NO; 310 | ENABLE_STRICT_OBJC_MSGSEND = YES; 311 | GCC_C_LANGUAGE_STANDARD = gnu99; 312 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 313 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 314 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 315 | GCC_WARN_UNDECLARED_SELECTOR = YES; 316 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 317 | GCC_WARN_UNUSED_FUNCTION = YES; 318 | GCC_WARN_UNUSED_VARIABLE = YES; 319 | MACOSX_DEPLOYMENT_TARGET = 10.10; 320 | MTL_ENABLE_DEBUG_INFO = NO; 321 | SDKROOT = macosx; 322 | }; 323 | name = Release; 324 | }; 325 | 367DEA221A20F39C00A65D14 /* Debug */ = { 326 | isa = XCBuildConfiguration; 327 | buildSettings = { 328 | COMBINE_HIDPI_IMAGES = YES; 329 | DEPLOYMENT_LOCATION = YES; 330 | DSTROOT = "$(HOME)"; 331 | INFOPLIST_FILE = MLAutoReplace/Info.plist; 332 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 333 | MACOSX_DEPLOYMENT_TARGET = 10.10; 334 | PRODUCT_NAME = "$(TARGET_NAME)"; 335 | WRAPPER_EXTENSION = xcplugin; 336 | }; 337 | name = Debug; 338 | }; 339 | 367DEA231A20F39C00A65D14 /* Release */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | COMBINE_HIDPI_IMAGES = YES; 343 | DEPLOYMENT_LOCATION = YES; 344 | DSTROOT = "$(HOME)"; 345 | INFOPLIST_FILE = MLAutoReplace/Info.plist; 346 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 347 | MACOSX_DEPLOYMENT_TARGET = 10.10; 348 | PRODUCT_NAME = "$(TARGET_NAME)"; 349 | WRAPPER_EXTENSION = xcplugin; 350 | }; 351 | name = Release; 352 | }; 353 | /* End XCBuildConfiguration section */ 354 | 355 | /* Begin XCConfigurationList section */ 356 | 367DEA0B1A20F39C00A65D14 /* Build configuration list for PBXProject "MLAutoReplace" */ = { 357 | isa = XCConfigurationList; 358 | buildConfigurations = ( 359 | 367DEA1F1A20F39C00A65D14 /* Debug */, 360 | 367DEA201A20F39C00A65D14 /* Release */, 361 | ); 362 | defaultConfigurationIsVisible = 0; 363 | defaultConfigurationName = Release; 364 | }; 365 | 367DEA211A20F39C00A65D14 /* Build configuration list for PBXNativeTarget "MLAutoReplace" */ = { 366 | isa = XCConfigurationList; 367 | buildConfigurations = ( 368 | 367DEA221A20F39C00A65D14 /* Debug */, 369 | 367DEA231A20F39C00A65D14 /* Release */, 370 | ); 371 | defaultConfigurationIsVisible = 0; 372 | defaultConfigurationName = Release; 373 | }; 374 | /* End XCConfigurationList section */ 375 | }; 376 | rootObject = 367DEA081A20F39C00A65D14 /* Project object */; 377 | } 378 | -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/project.xcworkspace/xcshareddata/MLAutoReplace.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | CFC95859-3679-45A5-9609-F41B61898919 9 | IDESourceControlProjectName 10 | MLAutoReplace 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 48F7138B22D07E2F0C6AB92CA43BD0C571F0E398 14 | github.com:molon/MLAutoReplace.git 15 | 16 | IDESourceControlProjectPath 17 | MLAutoReplace.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 48F7138B22D07E2F0C6AB92CA43BD0C571F0E398 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:molon/MLAutoReplace.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 48F7138B22D07E2F0C6AB92CA43BD0C571F0E398 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 48F7138B22D07E2F0C6AB92CA43BD0C571F0E398 36 | IDESourceControlWCCName 37 | MLAutoReplace 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/project.xcworkspace/xcuserdata/molon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/MLAutoReplace.xcodeproj/project.xcworkspace/xcuserdata/molon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/xcshareddata/xcschemes/MLAutoReplace.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 61 | 65 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/xcuserdata/molon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /MLAutoReplace.xcodeproj/xcuserdata/molon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SuppressBuildableAutocreation 6 | 7 | 367DEA0F1A20F39C00A65D14 8 | 9 | primary 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSDate+Addition.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Addition.h 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 15/5/5. 6 | // Copyright (c) 2015年 molon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSDate (Addition) 12 | 13 | + (NSString*)ml_nowString; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSDate+Addition.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Addition.m 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 15/5/5. 6 | // Copyright (c) 2015年 molon. All rights reserved. 7 | // 8 | 9 | #import "NSDate+Addition.h" 10 | 11 | @implementation NSDate (Addition) 12 | 13 | + (NSString*)ml_nowString 14 | { 15 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 16 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm"]; 17 | 18 | NSString *dateString = [dateFormatter stringFromDate:[NSDate date]]; 19 | return dateString; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSString+Addition.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Addition.h 3 | // 4 | // Created by Molon on 13-11-12. 5 | // Copyright (c) 2013年 Molon. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSString (Addition) 11 | 12 | + (BOOL)IsNilOrEmpty:(NSString *)str; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSString+Addition.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Addition.m 3 | // 4 | // Created by Molon on 13-11-12. 5 | // Copyright (c) 2013年 Molon. All rights reserved. 6 | // 7 | 8 | #import "NSString+Addition.h" 9 | 10 | @implementation NSString (Addition) 11 | 12 | + (BOOL)IsNilOrEmpty:(NSString *)str { 13 | if (![str isKindOfClass:[NSString class]]) { 14 | return YES; 15 | } 16 | 17 | if (str == nil||str.length<=0) { 18 | return YES; 19 | } 20 | return ([str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length<=0); 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSString+PDRegex.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+PDRegex.h 3 | // RegexOnNSString 4 | // 5 | // Created by Carl Brown on 10/3/11. 6 | // Copyright 2011 PDAgent, LLC. Released under MIT License. 7 | // 8 | 9 | #import 10 | 11 | //就是怕和VV的类重名而两者不能共存,不知道有没影响,为了保险起见。 12 | @interface NSString (RENAME_PDRegex) 13 | 14 | -(NSString *) vvv_stringByReplacingRegexPattern:(NSString *)regex withString:(NSString *) replacement; 15 | -(NSString *) vvv_stringByReplacingRegexPattern:(NSString *)regex withString:(NSString *) replacement caseInsensitive:(BOOL) ignoreCase; 16 | -(NSString *) vvv_stringByReplacingRegexPattern:(NSString *)regex withString:(NSString *) replacement caseInsensitive:(BOOL) ignoreCase treatAsOneLine:(BOOL) assumeMultiLine; 17 | -(NSArray *) vvv_stringsByExtractingGroupsUsingRegexPattern:(NSString *)regex; 18 | -(NSArray *) vvv_stringsByExtractingGroupsUsingRegexPattern:(NSString *)regex caseInsensitive:(BOOL) ignoreCase treatAsOneLine:(BOOL) assumeMultiLine; 19 | -(BOOL) vvv_matchesPatternRegexPattern:(NSString *)regex; 20 | -(BOOL) vvv_matchesPatternRegexPattern:(NSString *)regex caseInsensitive:(BOOL) ignoreCase treatAsOneLine:(BOOL) assumeMultiLine; 21 | -(NSString *) vvv_textUntilNextString:(NSString *)findString currentLocation:(NSInteger)location; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSString+PDRegex.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+PDRegex.m 3 | // RegexOnNSString 4 | // 5 | // Created by Carl Brown on 10/3/11. 6 | // Copyright 2011 PDAgent, LLC. Released under MIT License. 7 | // 8 | 9 | #import "NSString+PDRegex.h" 10 | 11 | @implementation NSString (PDRegex) 12 | 13 | -(NSString *) vvv_stringByReplacingRegexPattern:(NSString *)regex withString:(NSString *) replacement caseInsensitive:(BOOL)ignoreCase { 14 | return [self vvv_stringByReplacingRegexPattern:regex withString:replacement caseInsensitive:ignoreCase treatAsOneLine:NO]; 15 | } 16 | 17 | -(NSString *) vvv_stringByReplacingRegexPattern:(NSString *)regex withString:(NSString *) replacement caseInsensitive:(BOOL) ignoreCase treatAsOneLine:(BOOL) assumeMultiLine { 18 | 19 | NSUInteger options=0; 20 | if (ignoreCase) { 21 | options = options | NSRegularExpressionCaseInsensitive; 22 | } 23 | if (assumeMultiLine) { 24 | options = options | NSRegularExpressionDotMatchesLineSeparators; 25 | } 26 | 27 | NSError *error=nil; 28 | NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:&error]; 29 | if (error) { 30 | NSLog(@"Error creating Regex: %@",[error description]); 31 | return nil; 32 | } 33 | 34 | NSString *retVal= [pattern stringByReplacingMatchesInString:self options:0 range:NSMakeRange(0, [self length]) withTemplate:replacement]; 35 | return retVal; 36 | } 37 | 38 | -(NSString *) vvv_stringByReplacingRegexPattern:(NSString *)regex withString:(NSString *) replacement { 39 | return [self vvv_stringByReplacingRegexPattern:regex withString:replacement caseInsensitive:NO treatAsOneLine:NO]; 40 | } 41 | 42 | -(NSArray *) vvv_stringsByExtractingGroupsUsingRegexPattern:(NSString *)regex caseInsensitive:(BOOL) ignoreCase treatAsOneLine:(BOOL) assumeMultiLine { 43 | NSUInteger options=0; 44 | if (ignoreCase) { 45 | options = options | NSRegularExpressionCaseInsensitive; 46 | } 47 | if (assumeMultiLine) { 48 | options = options | NSRegularExpressionDotMatchesLineSeparators; 49 | } 50 | 51 | NSError *error=nil; 52 | NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:&error]; 53 | if (error) { 54 | NSLog(@"Error creating Regex: %@",[error description]); 55 | return nil; 56 | } 57 | 58 | __block NSMutableArray *retVal = [NSMutableArray array]; 59 | [pattern enumerateMatchesInString:self options:0 range:NSMakeRange(0, [self length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { 60 | //Note, we only want to return the things in parens, so we're skipping index 0 intentionally 61 | for (int i=1; i<[result numberOfRanges]; i++) { 62 | NSString *matchedString=[self substringWithRange:[result rangeAtIndex:i]]; 63 | [retVal addObject:matchedString]; 64 | } 65 | }]; 66 | return retVal; 67 | } 68 | 69 | -(NSArray *) vvv_stringsByExtractingGroupsUsingRegexPattern:(NSString *)regex { 70 | return [self vvv_stringsByExtractingGroupsUsingRegexPattern:regex caseInsensitive:NO treatAsOneLine:NO]; 71 | } 72 | 73 | -(BOOL) vvv_matchesPatternRegexPattern:(NSString *)regex caseInsensitive:(BOOL) ignoreCase treatAsOneLine:(BOOL) assumeMultiLine { 74 | NSUInteger options=0; 75 | if (ignoreCase) { 76 | options = options | NSRegularExpressionCaseInsensitive; 77 | } 78 | if (assumeMultiLine) { 79 | options = options | NSRegularExpressionDotMatchesLineSeparators; 80 | } 81 | 82 | NSError *error=nil; 83 | NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:&error]; 84 | if (error) { 85 | NSLog(@"Error creating Regex: %@",[error description]); 86 | return NO; //Can't possibly match an invalid Regex 87 | } 88 | 89 | return ([pattern numberOfMatchesInString:self options:0 range:NSMakeRange(0, [self length])] > 0); 90 | } 91 | 92 | -(BOOL) vvv_matchesPatternRegexPattern:(NSString *)regex { 93 | return [self vvv_matchesPatternRegexPattern:regex caseInsensitive:NO treatAsOneLine:NO]; 94 | } 95 | 96 | 97 | -(NSString *) vvv_textUntilNextString:(NSString *)findString currentLocation:(NSInteger)location 98 | { 99 | NSInteger curseLocation = location; 100 | 101 | NSRange range = NSMakeRange(curseLocation, self.length - curseLocation); 102 | NSRange nextLineRange = [self rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:0 range:range]; 103 | NSRange rangeToString = [self rangeOfString:findString options:0 range:range]; 104 | 105 | NSString *line = nil; 106 | if (nextLineRange.location != NSNotFound && rangeToString.location != NSNotFound && nextLineRange.location <= rangeToString.location) { 107 | NSRange lineRange = NSMakeRange(nextLineRange.location + 1, rangeToString.location - nextLineRange.location); 108 | if (lineRange.location < [self length] && NSMaxRange(lineRange) <= [self length]) { 109 | line = [self substringWithRange:lineRange]; 110 | return line; 111 | } else { 112 | return nil; 113 | } 114 | } else { 115 | return nil; 116 | } 117 | } 118 | @end 119 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSTextView+Addition.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextView+Addition.h 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 4/25/14. 6 | // Copyright (c) 2014 molon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSTextView (Addition) 12 | 13 | - (NSInteger)ml_currentCurseLocation; 14 | 15 | //get begin location of current curse location line 16 | - (NSUInteger)ml_beginLocationOfCurrentLine; 17 | 18 | //get end 19 | - (NSUInteger)ml_endLocationOfCurrentLine; 20 | 21 | //get text of current curse location line 22 | - (NSString *)ml_textOfCurrentLine; 23 | 24 | //从当前位置往后找字符串直到找到XXX为止 25 | -(NSString *)ml_textUntilNextString:(NSString *)findString; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /MLAutoReplace/Category/NSTextView+Addition.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextView+Addtion.m 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 4/25/14. 6 | // Copyright (c) 2014 molon. All rights reserved. 7 | // 8 | 9 | #import "NSTextView+Addition.h" 10 | #import "NSString+Addition.h" 11 | #import "NSString+PDRegex.h" 12 | 13 | @implementation NSTextView (Addition) 14 | 15 | - (NSInteger)ml_currentCurseLocation 16 | { 17 | return [[[self selectedRanges] objectAtIndex:0] rangeValue].location; 18 | } 19 | 20 | - (NSString *)ml_textOfCurrentLine 21 | { 22 | NSString *string = self.textStorage.string; 23 | if ([NSString IsNilOrEmpty:string]) { 24 | return nil; 25 | } 26 | 27 | NSInteger curseLocation = [self ml_currentCurseLocation]; 28 | NSRange range = NSMakeRange(0, curseLocation); 29 | 30 | NSUInteger thisLineBeginLocation = [string rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSBackwardsSearch range:range].location; 31 | thisLineBeginLocation = (thisLineBeginLocation==NSNotFound)?0:thisLineBeginLocation+1; 32 | 33 | range = NSMakeRange(thisLineBeginLocation, string.length-thisLineBeginLocation); 34 | 35 | NSUInteger thisLineEndLocation = [string rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch range:range].location; 36 | thisLineEndLocation = (thisLineEndLocation==NSNotFound)?string.length-1:thisLineEndLocation-1; 37 | 38 | NSRange lineRange = NSMakeRange(thisLineBeginLocation, thisLineEndLocation-thisLineBeginLocation+1); 39 | if (lineRange.location 2 | 3 | 4 | 5 | UIButton* 6 | - (UIButton *)<name> { 7 | if (!_<name>) { 8 | UIButton *button = [[UIButton alloc]init]; 9 | [button setTitle:@"<#title#>" forState:UIControlStateNormal]; 10 | [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 11 | [button addTarget:self action:@selector(<#action#>) forControlEvents:UIControlEventTouchUpInside]; 12 | 13 | <#custom#> 14 | 15 | _<name> = button; 16 | } 17 | return _<name>; 18 | } 19 | 20 | UIImageView* 21 | - (UIImageView *)<name> { 22 | if (!_<name>) { 23 | UIImageView *imageView = [[UIImageView alloc]init]; 24 | imageView.image = [UIImage imageNamed:@"<#imageName#>"]; 25 | imageView.contentMode = UIViewContentModeScaleAspectFill; 26 | <#custom#> 27 | 28 | _<name> = imageView; 29 | } 30 | return _<name>; 31 | } 32 | 33 | UILabel* 34 | - (UILabel *)<name> { 35 | if (!_<name>) { 36 | UILabel* label = [[UILabel alloc]init]; 37 | label.backgroundColor = [UIColor clearColor]; 38 | label.textColor = [UIColor blackColor]; 39 | label.font = [UIFont systemFontOfSize:14.0f]; 40 | 41 | <#custom#> 42 | 43 | _<name> = label; 44 | } 45 | return _<name>; 46 | } 47 | 48 | UITableView* 49 | - (UITableView *)<name> { 50 | if (!_<name>) { 51 | UITableView *tableView = [[UITableView alloc]init]; 52 | tableView.delegate = self; 53 | tableView.dataSource = self; 54 | 55 | _<name> = tableView; 56 | } 57 | return _<name>; 58 | } 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /MLAutoReplace/DefaultReplaceOther.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | regex 7 | ^\s*@s/$ 8 | replaceContent 9 | @property (nonatomic, strong) <#custom#> 10 | 11 | 12 | regex 13 | ^\s*@w/$ 14 | replaceContent 15 | @property (nonatomic, weak) <#custom#> 16 | 17 | 18 | regex 19 | ^\s*@a/$ 20 | replaceContent 21 | @property (nonatomic, assign) <#custom#> 22 | 23 | 24 | regex 25 | ^\s*@c/$ 26 | replaceContent 27 | @property (nonatomic, copy) NSString *<#custom#> 28 | 29 | 30 | regex 31 | ^\s*-\s*\(\s*id\s*\)\s*init\s*//$ 32 | replaceContent 33 | - (instancetype)init { 34 | self = [super init]; 35 | if (self) { 36 | <#statements#> 37 | } 38 | return self; 39 | } 40 | 41 | 42 | regex 43 | ^\s*-\s*\(\s*instancetype\s*\)\s*init\s*//$ 44 | replaceContent 45 | - (instancetype)init { 46 | self = [super init]; 47 | if (self) { 48 | <#statements#> 49 | } 50 | return self; 51 | } 52 | 53 | 54 | regex 55 | ^\s*--ts$ 56 | replaceContent 57 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 58 | return 0; 59 | } 60 | 61 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 62 | { 63 | static NSString *CellIdentifier = @"<#CellIdentifier#>"; 64 | <#UITableViewCell#> *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 65 | if (!cell) { 66 | cell = <#[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]#>; 67 | } 68 | 69 | <#custom#> 70 | 71 | return cell; 72 | } 73 | 74 | 75 | 76 | regex 77 | ^\s*--td$ 78 | replaceContent 79 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 80 | { 81 | return <#44.0f#>; 82 | } 83 | 84 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 85 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 86 | 87 | <#custom#> 88 | } 89 | 90 | 91 | 92 | regex 93 | ^\s*--bl$ 94 | replaceContent 95 | typedef <#returnType#>(^<#name#>)(<#arguments#>); 96 | 97 | 98 | regex 99 | ^\s*#p$ 100 | replaceContent 101 | #pragma mark - 102 | 103 | 104 | regex 105 | ^\s*--edit$ 106 | replaceContent 107 | //edited by molon : <datetime> 108 | 109 | 110 | regex 111 | ^\s*@[Pp]{2}$ 112 | replaceContent 113 | @protocol <declare_class_below> 114 | @end 115 | 116 | 117 | regex 118 | ^\s*@property\s*\(\s*nonatomic\s*(,\s*strong\s*|)\)\s*(NSArray|NSMutableArray|NSSet|NSMutableSet)\s*<\s*(\w+)\s*>$ 119 | replaceContent 120 | @property (nonatomic<{0}>) <{1}><<{2}> *><<{2}>> *<#name#> 121 | 122 | 123 | regex 124 | ^\s*@property\s*\(\s*nonatomic\s*(,\s*strong\s*|)\)\s*(NSDictionary|NSMutableDictionary)\s*<\s*(\w+)\s*>$ 125 | replaceContent 126 | @property (nonatomic<{0}>) <{1}><NSString *,<{2}> *><<{2}>> *<#name#> 127 | 128 | 129 | regex 130 | ([^\s]{1}.*)(NSArray|NSMutableArray|NSSet|NSMutableSet)\s*<\s*(\w+)\s*>(\]|)$ 131 | replaceContent 132 | <{0}><{1}><<{2}> *><<{2}>> 133 | 134 | 135 | regex 136 | ^\s*(NSArray|NSMutableArray|NSSet|NSMutableSet)\s*<\s*(\w+)\s*>$ 137 | replaceContent 138 | <{0}><<{1}> *><<{1}>> * 139 | 140 | 141 | regex 142 | ([^\s]{1}.*)(NSDictionary|NSMutableDictionary)\s*<\s*(\w+)\s*>(\]|)$ 143 | replaceContent 144 | <{0}><{1}><NSString *,<{2}> *><<{2}>> 145 | 146 | 147 | regex 148 | ^\s*(NSDictionary|NSMutableDictionary)\s*<\s*(\w+)\s*>$ 149 | replaceContent 150 | <{0}><NSString *,<{1}> *><<{1}>> * 151 | 152 | 153 | regex 154 | ^\s*-\s*\(\s*instancetype\s*\)\s*initWithFrame\:\(CGRect\)frame\s*//$ 155 | replaceContent 156 | - (instancetype)initWithFrame:(CGRect)frame { 157 | self = [super initWithFrame:frame]; 158 | if (self) { 159 | <#custom#> 160 | } 161 | return self; 162 | } 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /MLAutoReplace/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.molon.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.5 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | DVTPlugInCompatibilityUUIDs 26 | 27 | 63FC1C47-140D-42B0-BB4D-A10B2D225574 28 | 37B30044-3B14-46BA-ABAA-F01000C27B63 29 | 640F884E-CE55-4B40-87C0-8869546CAB7A 30 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 31 | AD68E85B-441B-4301-B564-A45E4919A6AD 32 | C4A681B0-4A26-480E-93EC-1218098B9AA0 33 | FEC992CC-CA4A-4CFD-8881-77300FCB848A 34 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 35 | A16FF353-8441-459E-A50C-B071F53F51B7 36 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 37 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 38 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 39 | E969541F-E6F9-4D25-8158-72DC3545A6C6 40 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 41 | 42 | LSMinimumSystemVersion 43 | $(MACOSX_DEPLOYMENT_TARGET) 44 | NSPrincipalClass 45 | MLAutoReplace 46 | XC4Compatible 47 | 48 | XCPluginHasUI 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /MLAutoReplace/KeyboardHelper/MLKeyboardEventSender.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLKeyboardEventSender.h 3 | // VVDocumenter-Xcode 4 | // 5 | // Created by 王 巍 on 13-7-26. 6 | // Copyright (c) 2013年 OneV's Den. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | * 改名是为了防止和VVDocumenter插件类名冲突 14 | */ 15 | @interface MLKeyboardEventSender : NSObject 16 | -(void) beginKeyBoradEvents; 17 | -(void) sendKeyCode:(NSInteger)keyCode; 18 | -(void) sendKeyCode:(NSInteger)keyCode withModifierCommand:(BOOL)command 19 | alt:(BOOL)alt 20 | shift:(BOOL)shift 21 | control:(BOOL)control; 22 | -(void) sendKeyCode:(NSInteger)keyCode withModifier:(NSInteger)modifierMask; 23 | -(void) endKeyBoradEvents; 24 | 25 | +(BOOL) useDvorakLayout; 26 | @end 27 | -------------------------------------------------------------------------------- /MLAutoReplace/KeyboardHelper/MLKeyboardEventSender.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLKeyboardEventSender.m 3 | // VVDocumenter-Xcode 4 | // 5 | // Created by 王 巍 on 13-7-26. 6 | // Copyright (c) 2013年 OneV's Den. All rights reserved. 7 | // 8 | 9 | #import "MLKeyboardEventSender.h" 10 | 11 | @interface MLKeyboardEventSender() 12 | { 13 | CGEventSourceRef _source; 14 | CGEventTapLocation _location; 15 | } 16 | @end 17 | 18 | @implementation MLKeyboardEventSender 19 | -(void) beginKeyBoradEvents 20 | { 21 | _source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); 22 | _location = kCGHIDEventTap; 23 | } 24 | 25 | -(void) sendKeyCode:(NSInteger)keyCode 26 | { 27 | [self sendKeyCode:keyCode withModifier:0]; 28 | } 29 | 30 | -(void) sendKeyCode:(NSInteger)keyCode withModifierCommand:(BOOL)command 31 | alt:(BOOL)alt 32 | shift:(BOOL)shift 33 | control:(BOOL)control 34 | { 35 | NSInteger modifier = 0; 36 | if (command) { 37 | modifier = modifier ^ kCGEventFlagMaskCommand; 38 | } 39 | if (alt) { 40 | modifier = modifier ^ kCGEventFlagMaskAlternate; 41 | } 42 | if (shift) { 43 | modifier = modifier ^ kCGEventFlagMaskShift; 44 | } 45 | if (control) { 46 | modifier = modifier ^ kCGEventFlagMaskControl; 47 | } 48 | 49 | [self sendKeyCode:keyCode withModifier:modifier]; 50 | } 51 | 52 | -(void) sendKeyCode:(NSInteger)keyCode withModifier:(NSInteger)modifierMask 53 | { 54 | NSAssert(_source != NULL, @"You should call -beginKeyBoradEvents before sending a key event"); 55 | CGEventRef event; 56 | event = CGEventCreateKeyboardEvent(_source, keyCode, true); 57 | CGEventSetFlags(event, modifierMask); 58 | CGEventPost(_location, event); 59 | CFRelease(event); 60 | 61 | event = CGEventCreateKeyboardEvent(_source, keyCode, false); 62 | CGEventSetFlags(event, modifierMask); 63 | CGEventPost(_location, event); 64 | CFRelease(event); 65 | } 66 | 67 | -(void) endKeyBoradEvents 68 | { 69 | NSAssert(_source != NULL, @"You should call -beginKeyBoradEvents before end current keyborad event"); 70 | CFRelease(_source); 71 | _source = nil; 72 | } 73 | 74 | +(BOOL) useDvorakLayout 75 | { 76 | TISInputSourceRef inputSource = TISCopyCurrentKeyboardLayoutInputSource(); 77 | NSString *layoutID = (__bridge NSString *)TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID); 78 | CFRelease(inputSource); 79 | 80 | if ([layoutID rangeOfString:@"Dvorak" options:NSCaseInsensitiveSearch].location != NSNotFound) { 81 | return YES; 82 | } else { 83 | return NO; 84 | } 85 | } 86 | @end 87 | -------------------------------------------------------------------------------- /MLAutoReplace/MLAutoReplace.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLAutoReplace.h 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 4/25/14. 6 | // Copyright (c) 2014 molon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MLAutoReplace : NSObject 12 | 13 | + (id)sharedInstance; 14 | - (BOOL)loadReplacePlist; 15 | + (void)showSimpleTips:(NSString*)tips; 16 | 17 | @end -------------------------------------------------------------------------------- /MLAutoReplace/MLAutoReplace.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLAutoReplace.m 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 4/25/14. 6 | // Copyright (c) 2014 molon. All rights reserved. 7 | // 8 | 9 | #import "MLAutoReplace.h" 10 | #import "MLKeyboardEventSender.h" 11 | #import "SettingWindowController.h" 12 | #import "NSTextView+Addition.h" 13 | #import "NSString+Addition.h" 14 | #import "NSString+PDRegex.h" 15 | #import "NSDate+Addition.h" 16 | 17 | #import "Debug.h" 18 | 19 | static MLAutoReplace *sharedPlugin; 20 | 21 | static BOOL isSourceTextViewClass(id obj) { 22 | return [obj isKindOfClass:NSClassFromString(@"DVTSourceTextView")]|| 23 | [obj isKindOfClass:NSClassFromString(@"IDESourceEditor.IDESourceEditorView")]; 24 | } 25 | 26 | @interface MLAutoReplace() 27 | 28 | @property (nonatomic, strong) NSBundle *bundle; 29 | @property (nonatomic, strong) NSDictionary *replaceGetters; 30 | @property (nonatomic, strong) NSArray *replaceOthers; 31 | 32 | @property (nonatomic, strong) id eventMonitor; 33 | 34 | @property (nonatomic, strong) SettingWindowController *settingWC; 35 | 36 | @property (nonatomic, assign) BOOL checkSwitch; 37 | 38 | @end 39 | 40 | @implementation MLAutoReplace 41 | 42 | + (void)pluginDidLoad:(NSBundle *)plugin 43 | { 44 | static dispatch_once_t onceToken; 45 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 46 | if ([currentApplicationName isEqual:@"Xcode"]) { 47 | dispatch_once(&onceToken, ^{ 48 | sharedPlugin = [[self alloc] initWithBundle:plugin]; 49 | }); 50 | } 51 | } 52 | 53 | + (id)sharedInstance { 54 | return sharedPlugin; 55 | } 56 | 57 | - (id)initWithBundle:(NSBundle *)plugin 58 | { 59 | if (self = [super init]) { 60 | // reference to plugin's bundle, for resource acccess 61 | self.bundle = plugin; 62 | 63 | [[NSNotificationCenter defaultCenter] addObserver:self 64 | selector:@selector(applicationDidFinishLaunching:) 65 | name:NSApplicationDidFinishLaunchingNotification 66 | object:nil]; 67 | } 68 | return self; 69 | } 70 | 71 | + (void)showSimpleTips:(NSString*)tips 72 | { 73 | NSAlert *alert = [[NSAlert alloc] init]; 74 | [alert addButtonWithTitle:@"OK"]; 75 | [alert setMessageText:@"MLAutoReplace"]; 76 | [alert setInformativeText:tips]; 77 | [alert setAlertStyle:NSWarningAlertStyle]; 78 | [alert runModal]; 79 | } 80 | 81 | - (void)applicationDidFinishLaunching: (NSNotification*) noti { 82 | //加载替换配置文件 83 | if (![self loadReplacePlist]) { 84 | [MLAutoReplace showSimpleTips:@"Load plist failed! Please retart Xcode to retry."]; 85 | return; 86 | } 87 | 88 | //添加按键检测 ,检测shift+command+|,用来自动去处理当前自动re-indent 89 | //当前的弊端是如果用户打开XCode之后从来木有编辑过一次程序,那就没有用 90 | __weak __typeof(self)weakSelf = self; 91 | self.eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^NSEvent *(NSEvent *incomingEvent) { 92 | __strong __typeof(weakSelf)sSelf = weakSelf; 93 | if ([incomingEvent type] == NSKeyDown && [incomingEvent keyCode] == kVK_ANSI_Backslash 94 | && (incomingEvent.modifierFlags&kCGEventFlagMaskShift)&&(incomingEvent.modifierFlags&kCGEventFlagMaskCommand)) { 95 | 96 | //如果设置里不需要此功能则返回 97 | if (![sSelf.settingWC isUseAutoReIntent]) { 98 | return incomingEvent; 99 | } 100 | 101 | if (!isSourceTextViewClass(incomingEvent.window.firstResponder)) { 102 | return incomingEvent; 103 | } 104 | 105 | // 不是NSTextView就没必要继续,新版本Xcode有改动 106 | if (![incomingEvent.window.firstResponder isKindOfClass:[NSTextView class]]) { 107 | return incomingEvent; 108 | } 109 | NSTextView *textView = (NSTextView *)incomingEvent.window.firstResponder; 110 | 111 | DLOG(@"按了shift+command+|,window:%@,windowNumber:%ld,并且执行自动re-indent",incomingEvent.window,incomingEvent.windowNumber); 112 | 113 | NSUInteger locationOfCurrentLine = [textView ml_beginLocationOfCurrentLine]; 114 | 115 | MLKeyboardEventSender *kes = [[MLKeyboardEventSender alloc] init]; 116 | [kes beginKeyBoradEvents]; 117 | 118 | //全选 119 | [kes sendKeyCode:kVK_ANSI_A withModifierCommand:YES alt:NO shift:NO control:NO]; 120 | //ReIndent 121 | [kes sendKeyCode:kVK_ANSI_I withModifierCommand:NO alt:NO shift:NO control:YES]; 122 | 123 | [kes endKeyBoradEvents]; 124 | 125 | //防止越界 126 | if (textView.textStorage.length<=locationOfCurrentLine) { 127 | return nil; 128 | } 129 | 130 | //光标移到原本所在行的头部位置,隔个0.1秒再触发,其实最终有可能不是目标位置,因为re-indent之后文本会被调整 131 | //差不多就成了 132 | double delayInSeconds = 0.1f; 133 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 134 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 135 | [textView setSelectedRange:NSMakeRange(locationOfCurrentLine, 0)]; 136 | }); 137 | 138 | //让默认行为无效 139 | return nil; 140 | }else if ([incomingEvent type] == NSKeyDown && [incomingEvent keyCode] == kVK_ANSI_Backslash 141 | && (incomingEvent.modifierFlags&kCGEventFlagMaskControl)&&(incomingEvent.modifierFlags&kCGEventFlagMaskCommand)&&(incomingEvent.modifierFlags&kCGEventFlagMaskAlternate)) { 142 | //Control+Alt+Command+| 快捷键重载plist 143 | [sSelf.settingWC reloadPlist:nil]; //简单调用下WC的方法即可 144 | } 145 | return incomingEvent; 146 | }]; 147 | 148 | self.checkSwitch = YES; 149 | //监控文本改变 150 | [[NSNotificationCenter defaultCenter] addObserver:self 151 | selector:@selector(textStorageDidChange:) 152 | name:NSTextDidChangeNotification 153 | object:nil]; 154 | 155 | // Sample Menu Item: 156 | NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"Window"]; 157 | if (menuItem) { 158 | [[menuItem submenu] addItem:[NSMenuItem separatorItem]]; 159 | NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:@"MLAutoReplace" action:@selector(doMenuAction) keyEquivalent:@""]; 160 | [actionMenuItem setTarget:self]; 161 | [[menuItem submenu] addItem:actionMenuItem]; 162 | } 163 | } 164 | 165 | - (SettingWindowController *)settingWC 166 | { 167 | if (!_settingWC) { 168 | SettingWindowController *wc = [SettingWindowController alloc]; 169 | wc = [wc initWithWindowNibName:@"SettingWindowController" owner:wc]; 170 | _settingWC = wc; 171 | } 172 | return _settingWC; 173 | } 174 | 175 | - (void)doMenuAction 176 | { 177 | [self.settingWC showWindow:nil]; 178 | } 179 | 180 | - (void)dealloc 181 | { 182 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 183 | 184 | [NSEvent removeMonitor:self.eventMonitor]; 185 | self.eventMonitor = nil; 186 | } 187 | 188 | #pragma mark - load replace plist 189 | - (BOOL)loadReplacePlist 190 | { 191 | //加载替换getter的plist文件 192 | NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0]; 193 | 194 | documentDirectory = [documentDirectory stringByAppendingPathComponent:@"XCodePluginSetting/MLAutoReplace/"];//添加储存的文件夹名字 195 | 196 | if (![[NSFileManager defaultManager] fileExistsAtPath:documentDirectory]){ 197 | NSError *error = nil; 198 | if(![[NSFileManager defaultManager] createDirectoryAtPath:documentDirectory withIntermediateDirectories:YES attributes:nil error:&error]){ 199 | NSLog(@"%@",error); 200 | return NO; 201 | } 202 | } 203 | 204 | //replaceGetters 205 | NSString *documentPath = [documentDirectory stringByAppendingString:@"/ReplaceGetter.plist"]; 206 | if (![[NSFileManager defaultManager] fileExistsAtPath:documentPath]){ 207 | //找到工程下的默认plist 208 | NSString *defaultReplaceGetterPlistPathOfBundle = [self.bundle pathForResource:@"DefaultReplaceGetter" ofType:@"plist"]; 209 | 210 | NSDictionary *defaultDict = [NSDictionary dictionaryWithContentsOfFile:defaultReplaceGetterPlistPathOfBundle]; 211 | 212 | if ([defaultDict writeToFile:documentPath atomically:YES]) { 213 | NSLog(@"归档到%@成功",documentPath); 214 | }else{ 215 | NSLog(@"归档到%@失败,defaulPlist路径为%@",documentPath,defaultReplaceGetterPlistPathOfBundle); 216 | return NO; 217 | } 218 | } 219 | 220 | NSDictionary *replaceGetters = [NSDictionary dictionaryWithContentsOfFile:documentPath]; 221 | 222 | //将其key全部都设置为无空格的 223 | NSMutableDictionary *finalReplaceGetters = [NSMutableDictionary dictionary]; 224 | for (NSString *key in [replaceGetters allKeys]) { 225 | NSString *value = replaceGetters[key]; 226 | //如果结尾不是回车就添加上 227 | if ([value characterAtIndex:value.length-1]!='\n') { 228 | value = [value stringByAppendingString:@"\n"]; 229 | } 230 | finalReplaceGetters[[key stringByReplacingOccurrencesOfString:@" " withString:@""]] = value; 231 | } 232 | 233 | //简单做下同步吧 234 | @synchronized(self.replaceGetters){ 235 | self.replaceGetters = finalReplaceGetters; 236 | } 237 | 238 | 239 | //replaceOthers 240 | documentPath = [documentDirectory stringByAppendingString:@"/ReplaceOther.plist"]; 241 | if (![[NSFileManager defaultManager] fileExistsAtPath:documentPath]){ 242 | //找到工程下的默认plist,是数组包含字典的形式 243 | //根目录数组是因为需要按顺序检测正则是否匹配,检测到了就无需匹配下面的 244 | 245 | //找到工程下的默认plist 246 | NSString *defaultReplaceOtherPlistPathOfBundle = [self.bundle pathForResource:@"DefaultReplaceOther" ofType:@"plist"]; 247 | 248 | NSArray *defaultArray = [NSArray arrayWithContentsOfFile:defaultReplaceOtherPlistPathOfBundle]; 249 | 250 | if ([defaultArray writeToFile:documentPath atomically:YES]) { 251 | NSLog(@"归档到%@成功",documentPath); 252 | }else{ 253 | NSLog(@"归档到%@失败,defaulPlist路径为%@",documentPath,defaultReplaceOtherPlistPathOfBundle); 254 | return NO; 255 | } 256 | } 257 | 258 | //简单做下同步吧 259 | @synchronized(self.replaceOthers){ 260 | self.replaceOthers = [NSArray arrayWithContentsOfFile:documentPath]; 261 | } 262 | 263 | return YES; 264 | } 265 | 266 | 267 | #pragma mark - text change monitor 268 | - (void)textStorageDidChange:(NSNotification *)noti { 269 | if (!self.checkSwitch) { 270 | return; 271 | } 272 | 273 | if (!isSourceTextViewClass([noti object])) { 274 | return; 275 | } 276 | 277 | // 不是NSTextView就没必要继续,新版本Xcode有改动 278 | if (![[noti object]isKindOfClass:[NSTextView class]]) { 279 | return; 280 | } 281 | 282 | //本来以下是在后台线程里做的,现在去掉了,实则不必要,也免得发生可能的不稳定。懒得深入研究。 283 | NSTextView *textView = (NSTextView *)[noti object]; 284 | if (![textView.window.firstResponder isEqual:textView]) { 285 | return; 286 | } 287 | 288 | NSString *currentLine = [textView ml_textOfCurrentLine]; 289 | //empty should be ignored 290 | if ([NSString IsNilOrEmpty:currentLine]) { 291 | return; 292 | } 293 | 294 | //getter replace 295 | if ([self checkAndReplaceGetterWithCurrentLine:currentLine ofTextView:textView]) { 296 | return; 297 | } 298 | //other replace 299 | [self checkAndReplaceOtherWithCurrentLine:currentLine ofTextView:textView]; 300 | } 301 | 302 | #pragma mark - check and replace 303 | - (BOOL)checkAndReplaceGetterWithCurrentLine:(NSString*)currentLine ofTextView:(NSTextView*)textView 304 | { 305 | //eg:- (UIView *)view/// 306 | static NSString *lastCurrentLine = nil; 307 | if(![currentLine vvv_matchesPatternRegexPattern:@"^\\s*-\\s*\\(\\s*\\w+\\s*\\*?\\s*\\)\\s*\\w+\\s*/{3}$"]){ 308 | lastCurrentLine = currentLine; 309 | return NO; 310 | } 311 | 312 | //这里用来保证是一个字一个字把最后三个/敲出来的,而不是复制啊,或者从中间敲的 313 | if(![[lastCurrentLine stringByAppendingString:@"/"]isEqualToString:currentLine]||[textView ml_endLocationOfCurrentLine]+1!=[textView ml_currentCurseLocation]){ 314 | lastCurrentLine = currentLine; 315 | return NO; 316 | } 317 | lastCurrentLine = currentLine; 318 | 319 | 320 | //get the return type of getter 321 | NSArray *array = [currentLine vvv_stringsByExtractingGroupsUsingRegexPattern:@"\\(\\s*(\\w+\\s*\\*?)\\s*\\)"]; 322 | if (array.count<=0) { 323 | return NO; 324 | } 325 | NSString *type = [array[0] stringByReplacingOccurrencesOfString:@" " withString:@""]; 326 | 327 | //get the name of getter 328 | array = [currentLine vvv_stringsByExtractingGroupsUsingRegexPattern:@"\\)\\s*(\\w+)\\s*/{3}$"]; 329 | if (array.count<=0) { 330 | return NO; 331 | } 332 | NSString *name = array[0]; 333 | 334 | NSLog(@"%@,%@",type,name); 335 | 336 | //根据type找到对应的替换文本 337 | NSString *replaceContent = nil; 338 | 339 | NSString * const defaultReplaceGetterOfScalar = @"\t<#custom#>\n}\n"; 340 | 341 | NSString * replaceGetter = nil; 342 | @synchronized(self.replaceGetters){ //简单同步 343 | replaceGetter = self.replaceGetters[type]; 344 | } 345 | if (replaceGetter) { 346 | replaceContent = [replaceGetter stringByReplacingOccurrencesOfString:@"" withString:name]; 347 | }else{ 348 | NSString *replaceGetter = defaultReplaceGetterOfScalar; 349 | if ([type hasSuffix:@"*"]||[type isEqualToString:@"id"]) { 350 | NSString * const defaultReplaceGetterOfPointer = @"\tif (!_) {%@\n\t\t<#custom#>\n\t}\n\treturn _;\n}\n"; 351 | NSString *otherContent = @""; 352 | if ([type hasSuffix:@"*"]) { 353 | NSString *typeWithoutStar = [[type substringToIndex:type.length-1]stringByReplacingOccurrencesOfString:@" " withString:@""]; 354 | otherContent = [NSString stringWithFormat:@"\n\t\t_ = [%@ new];",typeWithoutStar]; 355 | 356 | type = [[type substringToIndex:type.length-1] stringByAppendingString:@" *"]; 357 | } 358 | replaceGetter = [NSString stringWithFormat:defaultReplaceGetterOfPointer,otherContent]; 359 | } 360 | replaceContent = [[NSString stringWithFormat:@"- (%@) {\n%@",type,replaceGetter] stringByReplacingOccurrencesOfString:@"" withString:name]; 361 | } 362 | 363 | if ([NSString IsNilOrEmpty:replaceContent]) { 364 | return NO; 365 | } 366 | 367 | //时间标签会替换成当前时间 368 | if ([replaceContent rangeOfString:@""].location!=NSNotFound) { 369 | replaceContent = [replaceContent stringByReplacingOccurrencesOfString:@"" withString:[NSDate ml_nowString]]; 370 | } 371 | 372 | //按键以完成替换 373 | [self removeCurrentLineContentAndInputContent:replaceContent ofTextView:textView]; 374 | 375 | return YES; 376 | } 377 | 378 | - (BOOL)checkAndReplaceOtherWithCurrentLine:(NSString*)currentLine ofTextView:(NSTextView*)textView 379 | { 380 | //对于@s/,@w/,@a/作为默认的。如果存储的没找到就放在最后面,检测这三个默认的 381 | // NSArray * const defaultArray = @[ 382 | // @{ 383 | // @"regex":@"^\\s*@s/$", 384 | // @"replaceContent": @"@property (nonatomic, strong) <#custom#>" 385 | // } 386 | // , 387 | // @{ 388 | // @"regex":@"^\\s*@w/$", 389 | // @"replaceContent": @"@property (nonatomic, weak) <#custom#>" 390 | // } 391 | // , 392 | // @{ 393 | // @"regex":@"^\\s*@a/$", 394 | // @"replaceContent": @"@property (nonatomic, assign) <#custom#>" 395 | // } 396 | // , 397 | // ]; 398 | //找到工程下的默认plist,默认的三个存储在这里面 399 | NSString *defaultReplaceOtherPlistPathOfBundle = [self.bundle pathForResource:@"DefaultReplaceOther" ofType:@"plist"]; 400 | 401 | NSArray *defaultArray = [NSArray arrayWithContentsOfFile:defaultReplaceOtherPlistPathOfBundle]; 402 | 403 | NSMutableArray *finalReplaceOthers = nil; 404 | @synchronized(self.replaceOthers){ //简单同步 405 | finalReplaceOthers = [self.replaceOthers mutableCopy]; 406 | } 407 | if (finalReplaceOthers.count<=0) { 408 | finalReplaceOthers = [defaultArray mutableCopy]; 409 | } 410 | for (NSDictionary *aRegexDict in finalReplaceOthers) { 411 | //找到正则 412 | NSString *regex = aRegexDict[@"regex"]; 413 | //找到替换内容 414 | NSString *replaceContent = aRegexDict[@"replaceContent"]; 415 | if ([NSString IsNilOrEmpty:regex]||[NSString IsNilOrEmpty:replaceContent]) { 416 | continue; 417 | } 418 | 419 | //检测是否匹配 420 | if(![currentLine vvv_matchesPatternRegexPattern:regex]){ 421 | continue; 422 | } 423 | 424 | //时间标签会替换成当前时间 425 | if ([replaceContent rangeOfString:@""].location!=NSNotFound) { 426 | replaceContent = [replaceContent stringByReplacingOccurrencesOfString:@"" withString:[NSDate ml_nowString]]; 427 | } 428 | 429 | //如果有获取下面的类名的标签,必须继承自某父类才可以 430 | if ([replaceContent rangeOfString:@""].location!=NSNotFound) { 431 | //找到下面的最近的直到最近的一些特殊标记 432 | NSString *textUntilColon = [textView ml_textUntilNextString:@":"]; 433 | if (textUntilColon.length<=0) { 434 | continue; 435 | } 436 | //探测其是否满足 @interface XXX: 这样的格式,满足的话就拎出来XXX 437 | NSArray *array = [textUntilColon vvv_stringsByExtractingGroupsUsingRegexPattern:@"@interface\\s+(\\w+)\\s*:"]; 438 | if (array.count<=0) { 439 | continue; 440 | } 441 | replaceContent = [replaceContent stringByReplacingOccurrencesOfString:@"" withString:array[0]]; 442 | } 443 | 444 | if ([replaceContent vvv_matchesPatternRegexPattern:@"\\<\\{\\d+\\}\\>"]) { 445 | //有位置占位符 446 | NSArray *array = [currentLine vvv_stringsByExtractingGroupsUsingRegexPattern:regex]; 447 | if (array.count<=0) { 448 | continue; 449 | } 450 | //ok 我们挨个去替换 451 | for (NSInteger i=0; i",i] withString:element]; 454 | } 455 | //替换完了,但是还是发现有没找到的,就肯定还是有问题就啥也不做。 456 | if ([replaceContent vvv_matchesPatternRegexPattern:@"\\<\\{\\d+\\}\\>"]) { 457 | continue; 458 | } 459 | } 460 | 461 | //按键以完成替换 462 | [self removeCurrentLineContentAndInputContent:replaceContent ofTextView:textView]; 463 | return YES; 464 | 465 | } 466 | 467 | return NO; 468 | 469 | } 470 | 471 | #pragma mark - auto input content and remove orig conten of current line 472 | - (void)removeCurrentLineContentAndInputContent:(NSString*)replaceContent ofTextView:(NSTextView*)textView 473 | { 474 | //记录下光标位置,找到此行开头的位置 475 | NSUInteger currentLocation = [textView ml_beginLocationOfCurrentLine]; 476 | NSUInteger tabBeginLocation = currentLocation; 477 | 478 | //根据replaceContent里的内容检查是否需要自动Tab 479 | BOOL isNeedAutoTab = NO; 480 | if([replaceContent vvv_matchesPatternRegexPattern:@"<#\\w+#>"]){ 481 | isNeedAutoTab = YES; 482 | 483 | //找到第一个可tab的所在位置 484 | NSArray *array = [replaceContent vvv_stringsByExtractingGroupsUsingRegexPattern:@"(<#\\w+#>)"]; 485 | if (array.count<=0) { 486 | return; 487 | } 488 | NSUInteger index = [replaceContent rangeOfString:array[0]].location; 489 | if (index==NSNotFound) { 490 | isNeedAutoTab = NO; 491 | }else{ 492 | tabBeginLocation = currentLocation+index; 493 | } 494 | } 495 | 496 | DLOG(@"开始替换"); 497 | 498 | //保存以前剪切板内容 499 | NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; 500 | if (!pasteBoard) { 501 | return; 502 | } 503 | NSString *originPBString = [pasteBoard stringForType:NSPasteboardTypeString]; 504 | DLOG(@"原剪切板内容:%@",originPBString); 505 | 506 | //复制要添加内容到剪切板 507 | [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; 508 | [pasteBoard setString:replaceContent forType:NSStringPboardType]; 509 | DLOG(@"设置新剪切板内容:%@",replaceContent); 510 | 511 | self.checkSwitch = NO; 512 | dispatch_block_t block = ^{ 513 | MLKeyboardEventSender *kes = [[MLKeyboardEventSender alloc] init]; 514 | BOOL useDvorakLayout = [MLKeyboardEventSender useDvorakLayout]; 515 | 516 | [kes beginKeyBoradEvents]; 517 | 518 | //光标移到此行结束的位置,这样才能一次把一行都删去 519 | [textView setSelectedRange:NSMakeRange([textView ml_endLocationOfCurrentLine]+1, 0)]; 520 | //删掉当前这一行光标位置前面的内容 Command+Delete 521 | [kes sendKeyCode:kVK_Delete withModifierCommand:YES alt:NO shift:NO control:NO]; 522 | DLOG(@"删除此行"); 523 | 524 | //粘贴剪切板内容 525 | NSInteger kKeyVCode = useDvorakLayout?kVK_ANSI_Period : kVK_ANSI_V; 526 | [kes sendKeyCode:kKeyVCode withModifierCommand:YES alt:NO shift:NO control:NO]; 527 | DLOG(@"粘贴新内容:%@",replaceContent); 528 | 529 | //这个按键用来模拟下上个命令执行完毕了,然后需要还原剪切板 ,按键是同步进行的,所以接到F20的时候应该之前的都执行完毕了 530 | [kes sendKeyCode:kVK_F20]; 531 | 532 | [kes endKeyBoradEvents]; 533 | 534 | static id eventMonitor = nil; 535 | eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^NSEvent *(NSEvent *incomingEvent) { 536 | if ([incomingEvent type] == NSKeyDown && [incomingEvent keyCode] == kVK_F20) { 537 | [NSEvent removeMonitor:eventMonitor]; 538 | eventMonitor = nil; 539 | 540 | //还原剪切板 541 | [pasteBoard setString:originPBString forType:NSStringPboardType]; 542 | DLOG(@"还原剪切板内容:%@",originPBString); 543 | 544 | if (isNeedAutoTab) { 545 | [kes beginKeyBoradEvents]; 546 | 547 | //光标移到tab开始的位置 548 | [textView setSelectedRange:NSMakeRange(tabBeginLocation, 0)]; 549 | //Send a 'tab' after insert the doc. For our lazy programmers. :) 550 | [kes sendKeyCode:kVK_Tab]; 551 | DLOG(@"去tab位置"); 552 | 553 | [kes endKeyBoradEvents]; 554 | } 555 | 556 | self.checkSwitch = YES; 557 | 558 | //让默认行为无效 559 | return nil; 560 | } 561 | return incomingEvent; 562 | }]; 563 | }; 564 | 565 | //键盘操作放到主线程去做 566 | dispatch_async(dispatch_get_main_queue(), block); 567 | 568 | } 569 | 570 | @end 571 | -------------------------------------------------------------------------------- /MLAutoReplace/SettingWindowController/SettingWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingWindowController.h 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 4/26/14. 6 | // Copyright (c) 2014 molon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SettingWindowController : NSWindowController 12 | 13 | @property (nonatomic, assign, readonly) BOOL isUseAutoReIntent; 14 | 15 | - (IBAction)reloadPlist:(id)sender; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MLAutoReplace/SettingWindowController/SettingWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SettingWindowController.m 3 | // MLAutoReplace 4 | // 5 | // Created by molon on 4/26/14. 6 | // Copyright (c) 2014 molon. All rights reserved. 7 | // 8 | 9 | #import "SettingWindowController.h" 10 | #import "MLAutoReplace.h" 11 | #import "Debug.h" 12 | 13 | #define kEditPlistApplicationName @"Xcode" 14 | 15 | NSString * const kIsUseAutoReIntentUserDefaultKey = @"com.molon.kIsUseAutoReIntentUserDefaultKey"; 16 | 17 | @interface SettingWindowController () 18 | 19 | @property (weak) IBOutlet NSButton *useAutoReIndentCheckBox; 20 | @property (nonatomic, assign) BOOL isUseAutoReIntent; 21 | 22 | @end 23 | 24 | @implementation SettingWindowController 25 | 26 | - (id)initWithWindow:(NSWindow *)window 27 | { 28 | self = [super initWithWindow:window]; 29 | if (self) { 30 | // Initialization code here. 31 | id obj = [[NSUserDefaults standardUserDefaults] objectForKey:kIsUseAutoReIntentUserDefaultKey]; 32 | if (!obj||[obj isKindOfClass:[NSNull class]]) { 33 | obj = @(YES); 34 | } 35 | self.isUseAutoReIntent = [obj boolValue]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)windowDidLoad 41 | { 42 | [super windowDidLoad]; 43 | 44 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 45 | self.useAutoReIndentCheckBox.state = self.isUseAutoReIntent; 46 | } 47 | 48 | - (void)dealloc 49 | { 50 | DLOG(@"SettingWindowController dealloc"); 51 | } 52 | 53 | - (IBAction)openGetterPlist:(id)sender { 54 | //打开替换getter的plist文件 55 | NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0]; 56 | 57 | NSString *documentPath = [documentDirectory stringByAppendingPathComponent:@"XCodePluginSetting/MLAutoReplace/ReplaceGetter.plist"]; 58 | 59 | if (![[NSFileManager defaultManager] fileExistsAtPath:documentPath]){ 60 | NSString *tips = [NSString stringWithFormat:@"Please check whether .plist file exists.\n\nThe path:\n%@",documentPath]; 61 | [MLAutoReplace showSimpleTips:tips]; 62 | return; 63 | } 64 | 65 | [[NSWorkspace sharedWorkspace]openFile:documentPath withApplication:kEditPlistApplicationName]; 66 | } 67 | 68 | - (IBAction)openOtherPlist:(id)sender { 69 | //打开替换getter的plist文件 70 | NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0]; 71 | 72 | NSString *documentPath = [documentDirectory stringByAppendingPathComponent:@"XCodePluginSetting/MLAutoReplace/ReplaceOther.plist"]; 73 | 74 | if (![[NSFileManager defaultManager] fileExistsAtPath:documentPath]){ 75 | NSString *tips = [NSString stringWithFormat:@"Please check whether .plist file exists.\n\nThe path:\n%@",documentPath]; 76 | [MLAutoReplace showSimpleTips:tips]; 77 | return; 78 | } 79 | 80 | [[NSWorkspace sharedWorkspace]openFile:documentPath withApplication:kEditPlistApplicationName]; 81 | } 82 | 83 | - (IBAction)reloadPlist:(id)sender { 84 | if ([[MLAutoReplace sharedInstance]loadReplacePlist]) { 85 | [MLAutoReplace showSimpleTips:@"Reload the data of .plist successfuly!"]; 86 | }else{ 87 | [MLAutoReplace showSimpleTips:@"Reload the data of .plist failed! Please retry."]; 88 | } 89 | } 90 | 91 | - (IBAction)autoReIndentSwitch:(id)sender { 92 | NSButton *checkBox = (NSButton*)sender; 93 | 94 | self.isUseAutoReIntent = checkBox.state; 95 | } 96 | 97 | - (void)setIsUseAutoReIntent:(BOOL)isUseAutoReIntent 98 | { 99 | _isUseAutoReIntent = isUseAutoReIntent; 100 | 101 | [[NSUserDefaults standardUserDefaults]setObject:@(isUseAutoReIntent) forKey:kIsUseAutoReIntentUserDefaultKey]; 102 | [[NSUserDefaults standardUserDefaults]synchronize]; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /MLAutoReplace/SettingWindowController/SettingWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 33 | 43 | 53 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MLAutoReplace 2 | ============= 3 | 4 | Xcode plugin, Re-Intent, make you write code more quickly. 5 | Use a portion code of [VVDocumenter-Xcode](https://github.com/onevcat/VVDocumenter-Xcode). 6 | 7 | ##Overview 8 | You can use shortcut key `Shift+Command+\` to auto re-indent all source of the current edit file. 9 | 10 | You can custom other replacer with regex. 11 | ![regex replace](https://raw.githubusercontent.com/molon/MLAutoReplace/master/replaceOther.gif) 12 | ![regex replace](https://raw.githubusercontent.com/molon/MLAutoReplace/master/replaceTS.gif) 13 | ![pseudo-generic](https://raw.githubusercontent.com/molon/MLAutoReplace/master/pseudo-generic.gif) 14 | 15 | You can input common getter quickly. 16 | ![replace getter](https://raw.githubusercontent.com/molon/MLAutoReplace/master/replaceGetter.gif) 17 | 18 | ##How to install? 19 | Download this project and run. 20 | 21 | ##Regex replacer 22 | 23 | Exmple: 24 | ![replace getter](https://raw.githubusercontent.com/molon/MLAutoReplace/master/regex.png) 25 | This item means that plugin will replace `@s/` to `@property (nonatomic, strong) <#custom#>`. 26 | 27 | 28 | The plugin will detect the content of current input line. 29 | 30 | Some placeholders can be replace with context. 31 | 32 | - ``: current datetime, you can use it to mark your edit time. 33 | - ``: the class name of the first `@interface XXX :` below. 34 | - `<{0}>`,`<{1}>`...: these placeholders will be replaced with its corresponding position of regex result. 35 | 36 | A demo for pseudo-generic(https://github.com/ibireme/YYModel/issues/79): 37 | 38 | ![pseudo-generic](https://raw.githubusercontent.com/molon/MLAutoReplace/master/pseudo-generic.gif) 39 | 40 | It uses two styles of placeholders: 41 | 42 | ``: 43 | 44 | ``` 45 | regex: ^\s*@[Pp]{2}$ 46 | replaceContent: @protocol ; 47 | ``` 48 | 49 | `<{0}>`,`<{1}>`(location placeholders): 50 | 51 | ``` 52 | regex: ^\s*@property\s*\(\s*nonatomic\s*(,\s*strong\s*|)\)\s*(NSArray|NSMutableArray|NSSet|NSMutableSet)\s*<\s*(\w+)\s*>$ 53 | replaceContent: @property (nonatomic<{0}>) <{1}><<{2}> *><<{2}>> *<#name#> 54 | 55 | regex: ^\s*@property\s*\(\s*nonatomic\s*(,\s*strong\s*|)\)\s*(NSDictionary|NSMutableDictionary)\s*<\s*(\w+)\s*>$ 56 | replaceContent: @property (nonatomic<{0}>) <{1}> *><<{2}>> *<#name#> 57 | 58 | ``` 59 | 60 | ##Re-Indent 61 | 62 | Just can be quickly re-intent. 63 | 64 | If you find that press `Shift+Command+\` does nothing. 65 | Please ensure that the shortcut key setting of Re-Intent is default. 66 | ![re-intent shortcut key setting](https://raw.githubusercontent.com/molon/MLAutoReplace/master/re-intent-setting.png) 67 | 68 | ##Getter replacer 69 | 70 | **In fact, this feature can be implemented with location placeholders. But I am not willing to delete the old feature.** 71 | 72 | You need add your own common syntax to the getter replacer. 73 | ![replace getter](https://raw.githubusercontent.com/molon/MLAutoReplace/master/addReplaceGetter.gif) 74 | 75 | `` means the property name. 76 | `<#xxx#>` means where need to input in,it is recommended to provide. 77 | 78 | Exmple: 79 | 80 | ``` 81 | - (UIImageView *) 82 | { 83 | if (!_) { 84 | UIImageView *imageView = [[UIImageView alloc]init]; 85 | imageView.image = [UIImage imageNamed:@"<#imageName#>"]; 86 | imageView.contentMode = UIViewContentModeScaleAspectFill; 87 | <#custom#> 88 | 89 | _ = imageView; 90 | } 91 | return _; 92 | } 93 | ``` 94 | 95 | You must reload .plist file with shortcut `control+option+command+\` after editing and saving it. 96 | You can also reload it with the `Reload .plist Data` button in MLAutoReplace window. 97 | 98 | How to use: 99 | ``` 100 | - (UIImageView *)xxx/// 101 | ``` 102 | Dont forget `///` please. :) 103 | -------------------------------------------------------------------------------- /addReplaceGetter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/addReplaceGetter.gif -------------------------------------------------------------------------------- /pseudo-generic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/pseudo-generic.gif -------------------------------------------------------------------------------- /re-intent-setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/re-intent-setting.png -------------------------------------------------------------------------------- /regex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/regex.png -------------------------------------------------------------------------------- /replaceGetter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/replaceGetter.gif -------------------------------------------------------------------------------- /replaceOther.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/replaceOther.gif -------------------------------------------------------------------------------- /replaceTS.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molon/MLAutoReplace/38263f9d91642a781a1f7895b183f5a7ad4b23da/replaceTS.gif --------------------------------------------------------------------------------