Files
OSGKeyboard/OSGKeyboardShared/Typing/TypingAutocapitalization.swift
T
Rocky 38e5ad570d feat(typing): add English autocomplete and Chinese candidate expand panel
Offline English lexicon suggestions with autocapitalization, plus a same-height Chinese more-candidates grid over the key area.
2026-08-03 18:30:20 +08:00

70 lines
2.1 KiB
Swift
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.
// TypingAutocapitalization.swift
// OSGKeyboard · Shared
//
// Decides when the English letter page should arm Shift once for
// automatic capitalization (mirrors UITextAutocapitalizationType).
import Foundation
public enum TypingAutocapitalizationMode: String, Sendable, Equatable {
case none
case words
case sentences
case allCharacters
}
public enum TypingAutocapitalization: Sendable {
/// Whether Shift should be armed for the next Latin letter.
public static func shouldCapitalize(
precedingText: String?,
mode: TypingAutocapitalizationMode
) -> Bool {
switch mode {
case .none:
return false
case .allCharacters:
return true
case .words:
return needsWordCapitalization(precedingText)
case .sentences:
return needsSentenceCapitalization(precedingText)
}
}
// MARK: - Private
private static func needsWordCapitalization(_ preceding: String?) -> Bool {
guard let preceding, !preceding.isEmpty else { return true }
guard let last = preceding.last else { return true }
return last.isWhitespace || last.isNewline
}
private static func needsSentenceCapitalization(_ preceding: String?) -> Bool {
guard let preceding, !preceding.isEmpty else { return true }
// Walk backward past trailing whitespace / newlines; capitalize when
// the field is empty or the previous visible character ends a sentence.
var index = preceding.endIndex
var sawContent = false
while index > preceding.startIndex {
index = preceding.index(before: index)
let character = preceding[index]
if character.isWhitespace || character.isNewline {
continue
}
sawContent = true
return isSentenceTerminator(character)
}
return !sawContent
}
private static func isSentenceTerminator(_ character: Character) -> Bool {
switch character {
case ".", "!", "?", "…", "。", "", "":
return true
default:
return false
}
}
}