diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index 3633946..7d9af15 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -65,6 +65,8 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, - (void)viewDidLoad { [super viewDidLoad]; + // 撤销删除是“上一段删除操作”的临时状态;键盘被系统回收/重建或跨页面回来时应当清空,避免误显示。 + [[KBBackspaceUndoManager shared] registerNonClearAction]; [self setupUI]; self.suggestionEngine = [KBSuggestionEngine shared]; self.currentWord = @""; @@ -93,9 +95,18 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, - (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; + // 进入/重新进入输入界面时,清理上一次会话残留的撤销状态与缓存,避免显示“撤销删除”但实际上已不可撤销。 + [[KBBackspaceUndoManager shared] registerNonClearAction]; + [[KBInputBufferManager shared] resetWithText:@""]; [[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded]; - [[KBInputBufferManager shared] seedIfEmptyWithContextBefore:self.textDocumentProxy.documentContextBeforeInput - after:self.textDocumentProxy.documentContextAfterInput]; + // 注意:微信/QQ 等宿主的 documentContext 可能是“截断窗口”,这里只更新 liveText,不要把它当作全文 manualSnapshot。 + [[KBInputBufferManager shared] updateFromExternalContextBefore:self.textDocumentProxy.documentContextBeforeInput + after:self.textDocumentProxy.documentContextAfterInput]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [[KBBackspaceUndoManager shared] registerNonClearAction]; } - (void)textDidChange:(id)textInput { diff --git a/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m b/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m index 4fc4b1c..e2af2bf 100644 --- a/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m +++ b/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m @@ -11,11 +11,11 @@ static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35; static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06; -static const NSTimeInterval kKBBackspaceChunkStartDelay = 1.0; +static const NSTimeInterval kKBBackspaceChunkStartDelay = 0.6; static const NSTimeInterval kKBBackspaceChunkRepeatInterval = 0.1; -static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.4; -static const NSInteger kKBBackspaceChunkSize = 6; -static const NSInteger kKBBackspaceChunkSizeFast = 12; +static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.2; +static const NSInteger kKBBackspaceChunkSize = 8; +static const NSInteger kKBBackspaceChunkSizeFast = 16; static const CGFloat kKBBackspaceClearLabelCornerRadius = 8.0; static const CGFloat kKBBackspaceClearLabelHeight = 26.0; static const CGFloat kKBBackspaceClearLabelPaddingX = 10.0; @@ -210,34 +210,77 @@ typedef NS_ENUM(NSInteger, KBClearPhase) { whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; asciiWordSet = [NSCharacterSet characterSetWithCharactersInString: @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"]; - punctuationSet = [NSCharacterSet punctuationCharacterSet]; + NSMutableCharacterSet *punct = [[NSCharacterSet punctuationCharacterSet] mutableCopy]; + // 补齐常见中文/全角标点(避免 chunk 总是只删 1 个符号) + [punct addCharactersInString:@",。!?;:、()【】《》“”‘’·…—"]; + punctuationSet = [punct copy]; }); __block NSInteger deleteCount = 0; - __block KBBackspaceChunkClass chunkClass = KBBackspaceChunkClassUnknown; + typedef NS_ENUM(NSInteger, KBBackspaceChunkPhase) { + KBBackspaceChunkPhaseWhitespace = 0, + KBBackspaceChunkPhasePunctuation, + KBBackspaceChunkPhaseCore + }; + __block KBBackspaceChunkPhase phase = KBBackspaceChunkPhaseWhitespace; + __block KBBackspaceChunkClass coreClass = KBBackspaceChunkClassUnknown; + [context enumerateSubstringsInRange:NSMakeRange(0, context.length) options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) { if (substring.length == 0) { return; } - KBBackspaceChunkClass currentClass = KBBackspaceChunkClassOther; - if ([substring rangeOfCharacterFromSet:whitespaceSet].location != NSNotFound) { - currentClass = KBBackspaceChunkClassWhitespace; - } else if ([substring rangeOfCharacterFromSet:asciiWordSet].location != NSNotFound) { - currentClass = KBBackspaceChunkClassASCIIWord; - } else if ([substring rangeOfCharacterFromSet:punctuationSet].location != NSNotFound) { - currentClass = KBBackspaceChunkClassPunctuation; - } - - if (chunkClass == KBBackspaceChunkClassUnknown) { - chunkClass = currentClass; - } else if (chunkClass != currentClass) { + if (deleteCount >= maxCount) { *stop = YES; return; } - deleteCount += 1; + KBBackspaceChunkClass currentClass = KBBackspaceChunkClassOther; + if ([substring rangeOfCharacterFromSet:whitespaceSet].location != NSNotFound) { + currentClass = KBBackspaceChunkClassWhitespace; + } else if ([substring rangeOfCharacterFromSet:punctuationSet].location != NSNotFound) { + currentClass = KBBackspaceChunkClassPunctuation; + } else if ([substring rangeOfCharacterFromSet:asciiWordSet].location != NSNotFound) { + currentClass = KBBackspaceChunkClassASCIIWord; + } + + BOOL consumed = NO; + while (!consumed) { + if (phase == KBBackspaceChunkPhaseWhitespace) { + if (currentClass == KBBackspaceChunkClassWhitespace) { + deleteCount += 1; + consumed = YES; + } else { + phase = KBBackspaceChunkPhasePunctuation; + } + continue; + } + + if (phase == KBBackspaceChunkPhasePunctuation) { + if (currentClass == KBBackspaceChunkClassPunctuation) { + deleteCount += 1; + consumed = YES; + } else { + phase = KBBackspaceChunkPhaseCore; + } + continue; + } + + // phase == Core:连续删同一类(ASCII 单词 / 其它),让效果更像微信“几个字一组” + if (coreClass == KBBackspaceChunkClassUnknown) { + coreClass = currentClass; + } + if (currentClass != coreClass) { + *stop = YES; + consumed = YES; + continue; + } + deleteCount += 1; + consumed = YES; + } + if (deleteCount >= maxCount) { *stop = YES; + return; } }]; @@ -480,12 +523,23 @@ typedef NS_ENUM(NSInteger, KBClearPhase) { if (!ivc) { return; } id proxy = ivc.textDocumentProxy; NSInteger nextEmptyRounds = emptyRounds; - static NSCharacterSet *sentenceBoundarySet = nil; - static NSCharacterSet *whitespaceSet = nil; + static NSCharacterSet *stopBoundarySet = nil; + static NSCharacterSet *trailingBoundarySet = nil; + static NSCharacterSet *trailingWhitespaceSet = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?"]; - whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + // stopBoundary: 遇到这些符号就停(不删除它) + // - 句末符号:. ! ? 。!? + // - 省略号:…(中文里“……”常用作句/段落的停顿) + // - 换行:\n \r(段落边界,避免一次“清空”跨段把全文删完) + stopBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?…\n\r\u2028\u2029"]; + + // trailingBoundary: 允许作为“尾部句末符号”先删掉,再继续删上一句(更接近微信体验) + // 注意:不要把换行/省略号放进来,否则可能跨段/跨停顿继续删。 + trailingBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?"]; + + // trailingWhitespace: 只跳过空格/Tab(不包含换行,换行由 stopBoundarySet 处理) + trailingWhitespaceSet = [NSCharacterSet whitespaceCharacterSet]; }); KBClearPhase phase = self.backspaceClearPhase; @@ -496,10 +550,9 @@ typedef NS_ENUM(NSInteger, KBClearPhase) { NSString *before = proxy.documentContextBeforeInput ?: @""; if (before.length == 0) { nextEmptyRounds += 1; - // 即使 context 为空,也尝试删一次(某些宿主延迟更新) - [[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1]; - [[KBInputBufferManager shared] applyClearDeleteCount:1]; - deletedThisTick += 1; + // 宿主(微信/QQ 等)可能在长文本场景下返回空 context,即使还有很多内容。 + // 为了避免一次“清空”误删全文:一旦拿不到 before,就立刻停止本次清空。 + shouldStop = YES; break; } nextEmptyRounds = 0; @@ -520,8 +573,9 @@ typedef NS_ENUM(NSInteger, KBClearPhase) { }]; if (lastChar.length == 0) { break; } - BOOL isWhitespace = ([lastChar rangeOfCharacterFromSet:whitespaceSet].location != NSNotFound); - BOOL isBoundary = ([lastChar rangeOfCharacterFromSet:sentenceBoundarySet].location != NSNotFound); + BOOL isWhitespace = ([lastChar rangeOfCharacterFromSet:trailingWhitespaceSet].location != NSNotFound); + BOOL isStopBoundary = ([lastChar rangeOfCharacterFromSet:stopBoundarySet].location != NSNotFound); + BOOL isTrailingBoundary = ([lastChar rangeOfCharacterFromSet:trailingBoundarySet].location != NSNotFound); if (phase == KBClearPhaseSkipWhitespace) { if (isWhitespace) { @@ -534,7 +588,7 @@ typedef NS_ENUM(NSInteger, KBClearPhase) { } if (phase == KBClearPhaseSkipTrailingBoundary) { - if (isBoundary) { + if (isTrailingBoundary) { [[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1]; [[KBInputBufferManager shared] applyClearDeleteCount:1]; deletedThisTick += 1; @@ -544,7 +598,7 @@ typedef NS_ENUM(NSInteger, KBClearPhase) { } // phase == DeleteUntilBoundary - if (isBoundary) { + if (isStopBoundary) { shouldStop = YES; // 保留该句末符号 break; }