171 lines
5.3 KiB
Mathematica
171 lines
5.3 KiB
Mathematica
|
|
//
|
|||
|
|
// 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
|