Files
keyboard/CustomKeyboard/Utils/KBBackspaceUndoManager.m
2025-12-19 19:21:08 +08:00

171 lines
5.3 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBBackspaceUndoManager.m
// CustomKeyboard
//
#import "KBBackspaceUndoManager.h"
#import "KBResponderUtils.h"
NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspaceUndoStateDidChangeNotification";
@interface KBBackspaceUndoManager ()
@property (nonatomic, strong) NSMutableArray<NSString *> *segments; // deletion order (last -> first)
@property (nonatomic, assign) BOOL lastActionWasClear;
@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]) {
_segments = [NSMutableArray array];
}
return self;
}
- (void)recordClearWithContext:(NSString *)context {
if (context.length == 0) { return; }
NSString *segment = [self kb_segmentForClearFromContext:context];
if (segment.length == 0) { return; }
if (!self.lastActionWasClear) {
[self.segments removeAllObjects];
}
[self.segments addObject:segment];
self.lastActionWasClear = YES;
[self kb_updateHasUndo:YES];
}
- (void)performUndoFromResponder:(UIResponder *)responder {
if (self.segments.count == 0) { return; }
UIInputViewController *ivc = KBFindInputViewController(responder);
if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSString *text = [self kb_buildUndoText];
if (text.length == 0) { return; }
[proxy insertText:text];
[self.segments removeAllObjects];
self.lastActionWasClear = NO;
[self kb_updateHasUndo:NO];
}
- (void)registerNonClearAction {
self.lastActionWasClear = NO;
if (self.segments.count == 0) { return; }
[self.segments removeAllObjects];
[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];
}
- (NSString *)kb_segmentForClearFromContext:(NSString *)context {
NSInteger length = context.length;
if (length == 0) { return @""; }
static NSCharacterSet *sentenceBoundarySet = nil;
static NSCharacterSet *whitespaceSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;。!?;…\n"];
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
});
NSInteger end = length;
while (end > 0) {
unichar ch = [context characterAtIndex:end - 1];
if ([whitespaceSet characterIsMember:ch]) {
end -= 1;
} else {
break;
}
}
NSInteger searchEnd = end;
while (searchEnd > 0) {
unichar ch = [context characterAtIndex:searchEnd - 1];
if ([sentenceBoundarySet characterIsMember:ch]) {
searchEnd -= 1;
} else {
break;
}
}
NSInteger boundaryIndex = NSNotFound;
for (NSInteger i = searchEnd - 1; i >= 0; i--) {
unichar ch = [context characterAtIndex:i];
if ([sentenceBoundarySet characterIsMember:ch]) {
boundaryIndex = i;
break;
}
}
NSInteger start = (boundaryIndex == NSNotFound) ? 0 : (boundaryIndex + 1);
if (start >= length) { return @""; }
return [context substringFromIndex:start];
}
- (NSString *)kb_buildUndoText {
if (self.segments.count == 0) { return @""; }
NSArray<NSString *> *ordered = [[self.segments reverseObjectEnumerator] allObjects];
NSMutableString *result = [NSMutableString string];
for (NSInteger i = 0; i < ordered.count; i++) {
NSString *segment = ordered[i] ?: @"";
if (segment.length == 0) { continue; }
if (i < ordered.count - 1) {
segment = [self kb_replaceTrailingBoundaryWithComma:segment];
}
[result appendString:segment];
}
return result;
}
- (NSString *)kb_replaceTrailingBoundaryWithComma:(NSString *)segment {
if (segment.length == 0) { return segment; }
static NSCharacterSet *boundarySet = nil;
static NSCharacterSet *englishBoundarySet = nil;
static NSCharacterSet *whitespaceSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
boundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;。!?;…\n"];
englishBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;"];
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
});
NSInteger idx = segment.length - 1;
while (idx >= 0) {
unichar ch = [segment characterAtIndex:idx];
if ([whitespaceSet characterIsMember:ch]) {
idx -= 1;
continue;
}
if (![boundarySet characterIsMember:ch]) {
return segment;
}
NSString *comma = [englishBoundarySet characterIsMember:ch] ? @"," : @"";
NSMutableString *mutable = [segment mutableCopy];
NSRange r = NSMakeRange(idx, 1);
[mutable replaceCharactersInRange:r withString:comma];
return mutable;
}
return segment;
}
@end