// // KBBackspaceUndoManager.m // CustomKeyboard // #import "KBBackspaceUndoManager.h" #import "KBResponderUtils.h" NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspaceUndoStateDidChangeNotification"; @interface KBBackspaceUndoManager () @property (nonatomic, copy) NSString *undoText; @property (nonatomic, assign) NSInteger undoAfterLength; @property (nonatomic, assign) BOOL hasUndo; @end @implementation KBBackspaceUndoManager + (instancetype)shared { static KBBackspaceUndoManager *mgr = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mgr = [[KBBackspaceUndoManager alloc] init]; }); return mgr; } - (instancetype)init { if (self = [super init]) { _undoText = @""; _undoAfterLength = 0; } return self; } - (void)recordDeletionSnapshotBefore:(NSString *)before after:(NSString *)after { if (self.undoText.length > 0) { return; } NSString *safeBefore = before ?: @""; NSString *safeAfter = after ?: @""; NSString *full = [safeBefore stringByAppendingString:safeAfter]; if (full.length == 0) { return; } self.undoText = full; self.undoAfterLength = (NSInteger)safeAfter.length; } - (void)recordClearWithContextBefore:(NSString *)before after:(NSString *)after { if (self.undoText.length == 0) { NSString *safeBefore = before ?: @""; NSString *safeAfter = after ?: @""; NSString *full = [safeBefore stringByAppendingString:safeAfter]; if (full.length > 0) { self.undoText = full; self.undoAfterLength = (NSInteger)safeAfter.length; } } if (self.undoText.length == 0) { return; } [self kb_updateHasUndo:YES]; } - (void)performUndoFromResponder:(UIResponder *)responder { if (self.undoText.length == 0) { return; } UIInputViewController *ivc = KBFindInputViewController(responder); if (!ivc) { return; } id proxy = ivc.textDocumentProxy; [self kb_clearAllTextForProxy:proxy]; [proxy insertText:self.undoText]; if (self.undoAfterLength > 0 && [proxy respondsToSelector:@selector(adjustTextPositionByCharacterOffset:)]) { [proxy adjustTextPositionByCharacterOffset:-self.undoAfterLength]; } self.undoText = @""; self.undoAfterLength = 0; [self kb_updateHasUndo:NO]; } - (void)registerNonClearAction { if (self.undoText.length == 0) { return; } self.undoText = @""; self.undoAfterLength = 0; [self kb_updateHasUndo:NO]; } #pragma mark - Helpers - (void)kb_updateHasUndo:(BOOL)hasUndo { if (self.hasUndo == hasUndo) { return; } self.hasUndo = hasUndo; [[NSNotificationCenter defaultCenter] postNotificationName:KBBackspaceUndoStateDidChangeNotification object:self]; } static const NSInteger kKBUndoClearMaxRounds = 200; - (void)kb_clearAllTextForProxy:(id)proxy { if (!proxy) { return; } if ([proxy respondsToSelector:@selector(adjustTextPositionByCharacterOffset:)]) { NSInteger guard = 0; NSString *contextAfter = proxy.documentContextAfterInput ?: @""; while (contextAfter.length > 0 && guard < kKBUndoClearMaxRounds) { NSInteger offset = (NSInteger)contextAfter.length; [proxy adjustTextPositionByCharacterOffset:offset]; for (NSUInteger i = 0; i < contextAfter.length; i++) { [proxy deleteBackward]; } guard += 1; contextAfter = proxy.documentContextAfterInput ?: @""; } } NSInteger guard = 0; NSString *contextBefore = proxy.documentContextBeforeInput ?: @""; while (contextBefore.length > 0 && guard < kKBUndoClearMaxRounds) { for (NSUInteger i = 0; i < contextBefore.length; i++) { [proxy deleteBackward]; } guard += 1; contextBefore = proxy.documentContextBeforeInput ?: @""; } } @end