2025-12-19 19:21:08 +08:00
|
|
|
//
|
|
|
|
|
// KBBackspaceUndoManager.m
|
|
|
|
|
// CustomKeyboard
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#import "KBBackspaceUndoManager.h"
|
|
|
|
|
#import "KBResponderUtils.h"
|
|
|
|
|
|
|
|
|
|
NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspaceUndoStateDidChangeNotification";
|
|
|
|
|
|
|
|
|
|
@interface KBBackspaceUndoManager ()
|
2025-12-23 18:05:01 +08:00
|
|
|
@property (nonatomic, copy) NSString *undoText;
|
|
|
|
|
@property (nonatomic, assign) NSInteger undoAfterLength;
|
2025-12-19 19:21:08 +08:00
|
|
|
@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]) {
|
2025-12-23 18:05:01 +08:00
|
|
|
_undoText = @"";
|
|
|
|
|
_undoAfterLength = 0;
|
2025-12-19 19:21:08 +08:00
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 18:05:01 +08:00
|
|
|
- (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;
|
|
|
|
|
}
|
2025-12-19 19:21:08 +08:00
|
|
|
|
2025-12-23 18:05:01 +08:00
|
|
|
- (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;
|
|
|
|
|
}
|
2025-12-19 19:21:08 +08:00
|
|
|
}
|
2025-12-23 18:05:01 +08:00
|
|
|
if (self.undoText.length == 0) { return; }
|
2025-12-19 19:21:08 +08:00
|
|
|
[self kb_updateHasUndo:YES];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)performUndoFromResponder:(UIResponder *)responder {
|
2025-12-23 18:05:01 +08:00
|
|
|
if (self.undoText.length == 0) { return; }
|
2025-12-19 19:21:08 +08:00
|
|
|
UIInputViewController *ivc = KBFindInputViewController(responder);
|
|
|
|
|
if (!ivc) { return; }
|
|
|
|
|
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
2025-12-23 18:05:01 +08:00
|
|
|
[self kb_clearAllTextForProxy:proxy];
|
|
|
|
|
[proxy insertText:self.undoText];
|
|
|
|
|
if (self.undoAfterLength > 0 &&
|
|
|
|
|
[proxy respondsToSelector:@selector(adjustTextPositionByCharacterOffset:)]) {
|
|
|
|
|
[proxy adjustTextPositionByCharacterOffset:-self.undoAfterLength];
|
|
|
|
|
}
|
2025-12-19 19:21:08 +08:00
|
|
|
|
2025-12-23 18:05:01 +08:00
|
|
|
self.undoText = @"";
|
|
|
|
|
self.undoAfterLength = 0;
|
2025-12-19 19:21:08 +08:00
|
|
|
[self kb_updateHasUndo:NO];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)registerNonClearAction {
|
2025-12-23 18:05:01 +08:00
|
|
|
if (self.undoText.length == 0) { return; }
|
|
|
|
|
self.undoText = @"";
|
|
|
|
|
self.undoAfterLength = 0;
|
2025-12-19 19:21:08 +08:00
|
|
|
[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];
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 18:05:01 +08:00
|
|
|
static const NSInteger kKBUndoClearMaxRounds = 200;
|
|
|
|
|
|
|
|
|
|
- (void)kb_clearAllTextForProxy:(id<UITextDocumentProxy>)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 ?: @"";
|
2025-12-19 19:21:08 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 18:05:01 +08:00
|
|
|
NSInteger guard = 0;
|
|
|
|
|
NSString *contextBefore = proxy.documentContextBeforeInput ?: @"";
|
|
|
|
|
while (contextBefore.length > 0 && guard < kKBUndoClearMaxRounds) {
|
|
|
|
|
for (NSUInteger i = 0; i < contextBefore.length; i++) {
|
|
|
|
|
[proxy deleteBackward];
|
2025-12-19 19:21:08 +08:00
|
|
|
}
|
2025-12-23 18:05:01 +08:00
|
|
|
guard += 1;
|
|
|
|
|
contextBefore = proxy.documentContextBeforeInput ?: @"";
|
2025-12-19 19:21:08 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|