a8d58d8f0c
Custom polish styles can opt in to emotion-matched emoji (default off), with prompt-level opt-in detection so paste-only styles keep model-added emoji. Also include Volcengine API-Key ASR auth, voice-processing capture, PiP flash fix, and related keyboard Shift/haptics reliability work.
72 lines
2.2 KiB
Swift
72 lines
2.2 KiB
Swift
// 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 from the caret. Trailing spaces are ignored; a newline
|
||
// itself starts a new line (system .sentences behavior, e.g. Notes
|
||
// after Return). Otherwise capitalize only after a sentence terminator.
|
||
var index = preceding.endIndex
|
||
while index > preceding.startIndex {
|
||
index = preceding.index(before: index)
|
||
let character = preceding[index]
|
||
if character.isNewline {
|
||
return true
|
||
}
|
||
if character.isWhitespace {
|
||
continue
|
||
}
|
||
return isSentenceTerminator(character)
|
||
}
|
||
return true
|
||
}
|
||
|
||
private static func isSentenceTerminator(_ character: Character) -> Bool {
|
||
switch character {
|
||
case ".", "!", "?", "…", "。", "!", "?":
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
}
|