KeyHandler // Rewrite documentation in Chinese.

- Also "reinitCompositor() -> ensureCompositor()".
- Other term fixes may apply.
This commit is contained in:
ShikiSuen 2022-06-18 08:03:43 +08:00
parent a6aefb9cef
commit b9107b04e5
6 changed files with 389 additions and 218 deletions

View File

@ -96,15 +96,15 @@ class InputState {
/// .Committing:
class Committing: InputState {
private(set) var poppedText: String = ""
private(set) var textToCommit: String = ""
convenience init(poppedText: String) {
convenience init(textToCommit: String) {
self.init()
self.poppedText = poppedText
self.textToCommit = textToCommit
}
var description: String {
"<InputState.Committing poppedText:\(poppedText)>"
"<InputState.Committing textToCommit:\(textToCommit)>"
}
}
@ -164,7 +164,7 @@ class InputState {
/// .Inputting: 使Compositor
class Inputting: NotEmpty {
var poppedText: String = ""
var textToCommit: String = ""
var tooltip: String = ""
override init(composingBuffer: String, cursorIndex: Int) {
@ -172,7 +172,7 @@ class InputState {
}
override var description: String {
"<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>, poppedText:\(poppedText)>"
"<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>, textToCommit:\(textToCommit)>"
}
}

View File

@ -24,10 +24,15 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// 調
/// Megrez Tekkon
/// composer compositor
import Cocoa
// MARK: - Delegate.
// MARK: - (Delegate).
/// KeyHandler
protocol KeyHandlerDelegate {
func ctlCandidate() -> ctlCandidate
func keyHandler(
@ -38,45 +43,52 @@ protocol KeyHandlerDelegate {
-> Bool
}
// MARK: - Kernel.
// MARK: - (Kernel).
/// KeyHandler 調
class KeyHandler {
///
let kEpsilon: Double = 0.000001
let kMaxComposingBufferNeedsToWalkSize = Int(max(12, ceil(Double(mgrPrefs.composingBufferSize) / 2)))
var composer: Tekkon.Composer = .init()
var compositor: Megrez.Compositor
var currentLM: vChewing.LMInstantiator = .init()
var currentUOM: vChewing.LMUserOverride = .init()
var walkedAnchors: [Megrez.NodeAnchor] = []
///
let kMaxComposingBufferNeedsToWalkSize = Int(max(12, ceil(Double(mgrPrefs.composingBufferSize) / 2)))
var composer: Tekkon.Composer = .init() //
var compositor: Megrez.Compositor //
var currentLM: vChewing.LMInstantiator = .init() //
var currentUOM: vChewing.LMUserOverride = .init() //
var walkedAnchors: [Megrez.NodeAnchor] = [] //
/// (ctlInputMethod)便
var delegate: KeyHandlerDelegate?
/// InputMode
/// IME UserPrefs
var inputMode: InputMode = IME.currentInputMode {
willSet {
// ctlInputMethod:
//
let isCHS: Bool = (newValue == InputMode.imeModeCHS)
/// ctlInputMethod IME
IME.currentInputMode = newValue
mgrPrefs.mostRecentInputMode = IME.currentInputMode.rawValue
let isCHS: Bool = (newValue == InputMode.imeModeCHS)
// Reinitiate language models if necessary
///
currentLM = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT
currentUOM = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT
// Synchronize the sub-languageModel state settings to the new LM.
///
syncBaseLMPrefs()
// Create new compositor and clear the composer.
// When it recreates, it adapts to the latest imeMode settings.
// This allows it to work with correct LMs.
reinitCompositor()
///
///
ensureCompositor()
ensureParser()
}
}
///
public init() {
/// ensureCompositor()
compositor = Megrez.Compositor(lm: currentLM, separator: "-")
///
ensureParser()
// defer willSet
/// inputMode
/// defer willSet
defer { inputMode = IME.currentInputMode }
}
@ -88,18 +100,22 @@ class KeyHandler {
// MARK: - Functions dealing with Megrez.
/// Megrez 使便
///
/// 使 Node Crossing
var actualCandidateCursorIndex: Int {
mgrPrefs.useRearCursorMode ? min(compositorCursorIndex, compositorLength - 1) : max(compositorCursorIndex, 1)
}
///
///
/// Viterbi
///
///
func walk() {
// Retrieve the most likely grid, i.e. a Maximum Likelihood Estimation
// of the best possible Mandarin characters given the input syllables,
// using the Viterbi algorithm implemented in the Megrez library.
// The walk() traces the grid to the end.
walkedAnchors = compositor.walk()
// if DEBUG mode is enabled, a GraphViz file is written to kGraphVizOutputfile.
// GraphViz
if mgrPrefs.isDebugModeEnabled {
let result = compositor.grid.dumpDOT
do {
@ -113,29 +129,31 @@ class KeyHandler {
}
}
///
///
/// Viterbi 使 O(N^2)
/// 使
/// 使
///
var popOverflowComposingTextAndWalk: String {
// In ideal situations we can allow users to type infinitely in a buffer.
// However, Viberti algorithm has a complexity of O(N^2), the walk will
// become slower as the number of nodes increase. Therefore, we need to
// auto-commit overflown texts which usually lose their influence over
// the whole MLE anyway -- so that when the user type along, the already
// composed text in the rear side of the buffer will be committed out.
// (i.e. popped out.)
var poppedText = ""
var textToCommit = ""
if compositor.grid.width > mgrPrefs.composingBufferSize {
if !walkedAnchors.isEmpty {
let anchor: Megrez.NodeAnchor = walkedAnchors[0]
if let theNode = anchor.node {
poppedText = theNode.currentKeyValue.value
textToCommit = theNode.currentKeyValue.value
}
compositor.removeHeadReadings(count: anchor.spanningLength)
}
}
walk()
return poppedText
return textToCommit
}
///
/// - Parameter key:
/// - Returns:
/// nil
func buildAssociatePhraseArray(withKey key: String) -> [String] {
var arrResult: [String] = []
if currentLM.hasAssociatedPhrasesForKey(key) {
@ -144,6 +162,11 @@ class KeyHandler {
return arrResult
}
///
///
/// - Parameters:
/// - value:
/// - respectCursorPushing: true
func fixNode(value: String, respectCursorPushing: Bool = true) {
let cursorIndex = min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength)
compositor.grid.fixNodeSelectedCandidate(location: cursorIndex, value: value)
@ -153,9 +176,7 @@ class KeyHandler {
// )
// //
// if !mgrPrefs.useSCPCTypingMode {
// // If the length of the readings and the characters do not match,
// // it often means it is a special symbol and it should not be stored
// // in the user override model.
// //
// var addToUserOverrideModel = true
// if selectedNode.spanningLength != value.count {
// IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
@ -163,7 +184,7 @@ class KeyHandler {
// }
// if addToUserOverrideModel {
// if let theNode = selectedNode.node {
// // SymbolLM Score -12
// // SymbolLM Score -12
// if theNode.scoreFor(candidate: value) <= -12 {
// IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
// addToUserOverrideModel = false
@ -172,6 +193,8 @@ class KeyHandler {
// }
// if addToUserOverrideModel {
// IME.prtDebugIntel("UOM: Start Observation.")
// // trigram
// // trigram
// currentUOM.observe(
// walkedNodes: walkedAnchors, cursorIndex: cursorIndex, candidate: value,
// timestamp: NSDate().timeIntervalSince1970
@ -180,6 +203,7 @@ class KeyHandler {
// }
walk()
///
if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing {
var nextPosition = 0
for node in walkedAnchors {
@ -192,23 +216,26 @@ class KeyHandler {
}
}
///
func markNodesFixedIfNecessary() {
let width = compositor.grid.width
if width <= kMaxComposingBufferNeedsToWalkSize {
return
}
var index: Int = 0
var index = 0
for anchor in walkedAnchors {
guard let node = anchor.node else { break }
if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
if node.score < node.kSelectedCandidateScore {
compositor.grid.fixNodeSelectedCandidate(
location: index + anchor.spanningLength, value: node.currentKeyValue.value)
location: index + anchor.spanningLength, value: node.currentKeyValue.value
)
}
index += anchor.spanningLength
}
}
///
var candidatesArray: [String] {
var arrCandidates: [String] = []
var arrNodes: [Megrez.NodeAnchor] = []
@ -234,7 +261,9 @@ class KeyHandler {
return arrCandidates
}
///
func dealWithOverrideModelSuggestions() {
/// trigram
let overrideValue =
mgrPrefs.useSCPCTypingMode
? ""
@ -243,6 +272,7 @@ class KeyHandler {
timestamp: NSDate().timeIntervalSince1970
)
///
if !overrideValue.isEmpty {
IME.prtDebugIntel(
"UOM: Suggestion retrieved, overriding the node score of the selected candidate.")
@ -256,6 +286,11 @@ class KeyHandler {
}
}
///
/// - Parameters:
/// - nodes:
/// - epsilon:
/// - Returns:
func findHighestScore(nodes: [Megrez.NodeAnchor], epsilon: Double) -> Double {
var highestScore: Double = 0
for currentAnchor in nodes {
@ -271,10 +306,12 @@ class KeyHandler {
// MARK: - Extracted methods and functions (Tekkon).
/// _
var currentMandarinParser: String {
mgrPrefs.mandarinParserName + "_"
}
///
func ensureParser() {
switch mgrPrefs.mandarinParser {
case MandarinParser.ofStandard.rawValue:
@ -310,7 +347,11 @@ class KeyHandler {
composer.clear()
}
/// Ruby
/// Ruby
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
func cnvZhuyinKeyToTextbookReading(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = []
for neta in target.split(separator: "-") {
@ -324,7 +365,11 @@ class KeyHandler {
return arrReturn.joined(separator: newSeparator)
}
// Ruby
/// Ruby
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
func restoreToneOneInZhuyinKey(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = []
for neta in target.split(separator: "-") {
@ -339,8 +384,10 @@ class KeyHandler {
// MARK: - Extracted methods and functions (Megrez).
var isCompositorEmpty: Bool { compositor.grid.width == 0 }
///
var isCompositorEmpty: Bool { compositor.isEmpty }
///
var rawNodes: [Megrez.NodeAnchor] {
/// 使 nodesCrossing macOS
/// nodeCrossing
@ -349,45 +396,54 @@ class KeyHandler {
: compositor.grid.nodesEndingAt(location: actualCandidateCursorIndex)
}
///
func syncBaseLMPrefs() {
currentLM.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled
currentLM.isCNSEnabled = mgrPrefs.cns11643Enabled
currentLM.isSymbolEnabled = mgrPrefs.symbolInputEnabled
}
func reinitCompositor() {
// Each Mandarin syllable is separated by a hyphen.
/// 使
func ensureCompositor() {
// 西
compositor = Megrez.Compositor(lm: currentLM, separator: "-")
}
///
var currentReadings: [String] { compositor.readings }
///
func ifLangModelHasUnigrams(forKey reading: String) -> Bool {
currentLM.hasUnigramsFor(key: reading)
}
///
func insertToCompositorAtCursor(reading: String) {
compositor.insertReadingAtCursor(reading: reading)
}
///
var compositorCursorIndex: Int {
get { compositor.cursorIndex }
set { compositor.cursorIndex = newValue }
}
///
var compositorLength: Int {
compositor.length
}
func deleteBuilderReadingInFrontOfCursor() {
///
///
/// Rear
func deleteCompositorReadingAtTheRearOfCursor() {
compositor.deleteReadingAtTheRearOfCursor()
}
func deleteBuilderReadingToTheFrontOfCursor() {
///
///
/// Front
func deleteCompositorReadingToTheFrontOfCursor() {
compositor.deleteReadingToTheFrontOfCursor()
}
var keyLengthAtIndexZero: Int {
walkedAnchors[0].node?.currentKeyValue.value.count ?? 0
}
}

View File

@ -24,9 +24,11 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// 調
import Cocoa
// MARK: - § Handle Candidate State.
// MARK: - § 調 (Handle Candidate State).
extension KeyHandler {
func handleCandidate(
@ -43,7 +45,7 @@ extension KeyHandler {
return true
}
// MARK: Cancel Candidate
// MARK: (Cancel Candidate)
let cancelCandidateKey =
input.isBackSpace || input.isESC || input.isDelete
@ -286,7 +288,7 @@ extension KeyHandler {
}
}
// MARK: - Associated Phrases
// MARK: (Associated Phrases)
if state is InputState.AssociatedPhrases {
if !input.isShiftHold { return false }
@ -322,9 +324,13 @@ extension KeyHandler {
if state is InputState.AssociatedPhrases { return false }
// MARK: SCPC Mode Processing
// MARK: (SCPC Mode Processing)
if mgrPrefs.useSCPCTypingMode {
///
/// - /
/// -
var punctuationNamePrefix = ""
if input.isOptionHold && !input.isControlHold {
@ -346,6 +352,8 @@ extension KeyHandler {
]
let customPunctuation: String = arrCustomPunctuations.joined(separator: "")
///
let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))]
let punctuation: String = arrPunctuations.joined(separator: "")

View File

@ -24,9 +24,12 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// 調 IMK 調
/// 調
import Cocoa
// MARK: - § Handle Input with States.
// MARK: - § 調 (Handle Input with States)
extension KeyHandler {
func handle(
@ -36,10 +39,9 @@ extension KeyHandler {
errorCallback: @escaping () -> Void
) -> Bool {
let charCode: UniChar = input.charCode
var state = state // Turn this incoming constant into variable.
var state = state //
// Ignore the input if its inputText is empty.
// Reason: such inputs may be functional key combinations.
// inputTest
guard let inputText: String = input.inputText, !inputText.isEmpty else {
return false
}
@ -52,8 +54,7 @@ extension KeyHandler {
return true
}
// Ignore the input if the composing buffer is empty with no reading
// and there is some function key combination.
//
let isFunctionKey: Bool =
input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNumericPad)
if !(state is InputState.NotEmpty) && !(state is InputState.AssociatedPhrases) && isFunctionKey {
@ -62,37 +63,40 @@ extension KeyHandler {
// MARK: Caps Lock processing.
// If Caps Lock is ON, temporarily disable phonetic reading.
// Note: Alphanumerical mode processing.
/// Caps Lock
/// Shift Chromium
/// IMK Shift 使
/// Caps Lock
if input.isBackSpace || input.isEnter || input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey
|| input.isExtraChooseCandidateKeyReverse || input.isCursorForward || input.isCursorBackward
{
// Do nothing if backspace is pressed -- we ignore the key
// BackSpace
} else if input.isCapsLockOn {
// Process all possible combination, we hope.
//
clear()
stateCallback(InputState.Empty())
// When shift is pressed, don't do further processing...
// ...since it outputs capital letter anyway.
// Shift
if input.isShiftHold {
return false
}
// If ASCII but not printable, don't use insertText:replacementRange:
// Certain apps don't handle non-ASCII char insertions.
/// ASCII 使insertText:replacementRange:
/// ASCII
/// Objective-C isPrintable()
/// CTools.h Swift
if charCode < 0x80, !CTools.isPrintable(charCode) {
return false
}
// Commit the entire input buffer.
stateCallback(InputState.Committing(poppedText: inputText.lowercased()))
//
stateCallback(InputState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty())
return true
}
// MARK: Numeric Pad Processing.
// MARK: (Numeric Pad Processing)
if input.isNumericPad {
if !input.isLeft, !input.isRight, !input.isDown,
@ -100,13 +104,13 @@ extension KeyHandler {
{
clear()
stateCallback(InputState.Empty())
stateCallback(InputState.Committing(poppedText: inputText.lowercased()))
stateCallback(InputState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty())
return true
}
}
// MARK: Handle Candidates.
// MARK: (Handle Candidates)
if state is InputState.ChoosingCandidate {
return handleCandidate(
@ -114,7 +118,7 @@ extension KeyHandler {
)
}
// MARK: Handle Associated Phrases.
// MARK: (Handle Associated Phrases)
if state is InputState.AssociatedPhrases {
if handleCandidate(
@ -126,7 +130,7 @@ extension KeyHandler {
}
}
// MARK: Handle Marking.
// MARK: 便使() (Handle Marking)
if let marking = state as? InputState.Marking {
if handleMarkingState(
@ -139,19 +143,20 @@ extension KeyHandler {
stateCallback(state)
}
// MARK: Handle BPMF Keys.
// MARK: (Handle BPMF Keys)
var keyConsumedByReading = false
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold
// See if Phonetic reading is valid.
// inputValidityCheck() charCode UniChar
// keyConsumedByReading
// composer.receiveKey() String UniChar
if !skipPhoneticHandling && composer.inputValidityCheck(key: charCode) {
composer.receiveKey(fromCharCode: charCode)
keyConsumedByReading = true
// If we have a tone marker, we have to insert the reading to the
// compositor in other words, if we don't have a tone marker, we just
// update the composing buffer.
// 調 updateClientComposingBuffer() return true
// 調
let composeReading = composer.hasToneMarker()
if !composeReading {
stateCallback(buildInputtingState)
@ -161,44 +166,49 @@ extension KeyHandler {
var composeReading = composer.hasToneMarker() //
// See if we have composition if Enter/Space is hit and buffer is not empty.
// We use "|=" conditioning so that the tone marker key is also taken into account.
// However, Swift does not support "|=".
// Enter Space _composer
// |=
composeReading = composeReading || (!composer.isEmpty && (input.isSpace || input.isEnter))
if composeReading {
if input.isSpace, !composer.hasToneMarker() {
composer.receiveKey(fromString: " ") //
// 調
// 使 OVMandarin調
composer.receiveKey(fromString: " ")
}
let reading = composer.getComposition()
let reading = composer.getComposition() //
//
// See whether we have a unigram for this...
//
if !ifLangModelHasUnigrams(forKey: reading) {
IME.prtDebugIntel("B49C0979語彙庫內無「\(reading)」的匹配記錄。")
errorCallback()
composer.clear()
//
stateCallback((compositorLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
return true
return true // IMK
}
// ... and insert it into the grid...
//
insertToCompositorAtCursor(reading: reading)
// ... then walk the grid...
let poppedText = popOverflowComposingTextAndWalk
//
let textToCommit = popOverflowComposingTextAndWalk
// ... get and tweak override model suggestion if possible...
//
// dealWithOverrideModelSuggestions() // 使
// ... fix nodes if necessary...
//
markNodesFixedIfNecessary()
// ... then update the text.
//
composer.clear()
// updateClientComposingBuffer()
let inputting = buildInputtingState
inputting.poppedText = poppedText
inputting.textToCommit = textToCommit
stateCallback(inputting)
///
if mgrPrefs.useSCPCTypingMode {
let choosingCandidates: InputState.ChoosingCandidate = buildCandidate(
state: inputting,
@ -207,7 +217,7 @@ extension KeyHandler {
if choosingCandidates.candidates.count == 1 {
clear()
let text: String = choosingCandidates.candidates.first ?? ""
stateCallback(InputState.Committing(poppedText: text))
stateCallback(InputState.Committing(textToCommit: text))
if !mgrPrefs.associatedPhrasesEnabled {
stateCallback(InputState.Empty())
@ -227,42 +237,44 @@ extension KeyHandler {
stateCallback(choosingCandidates)
}
}
return true // Telling the client that the key is consumed.
// ctlInputMethod IMK
return true
}
// The only possibility for this to be true is that the Phonetic reading
// already has a tone marker but the last key is *not* a tone marker key. An
// example is the sequence "6u" with the Standard layout, which produces "ˊ"
// but does not compose. Only sequences such as "ˊ", "ˊˊ", "ˊˇ", or "ˊ "
// would compose.
/// true 調調
/// 6jˊˊ調
/// 調ˊˊˊˊˇˊ
if keyConsumedByReading {
// updateClientComposingBuffer()
stateCallback(buildInputtingState)
return true
}
// MARK: Calling candidate window using Up / Down or PageUp / PageDn.
//
if let currentState = state as? InputState.NotEmpty, composer.isEmpty,
input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse || input.isSpace
|| input.isPageDown || input.isPageUp || (input.isTab && mgrPrefs.specifyShiftTabKeyBehavior)
|| (input.isTypingVertical && (input.isverticalTypingOnlyChooseCandidateKey))
{
if input.isSpace {
// If the Space key is NOT set to be a selection key
/// Space
if !mgrPrefs.chooseCandidateUsingSpace {
if compositorCursorIndex >= compositorLength {
let composingBuffer = currentState.composingBuffer
if !composingBuffer.isEmpty {
stateCallback(InputState.Committing(poppedText: composingBuffer))
stateCallback(InputState.Committing(textToCommit: composingBuffer))
}
clear()
stateCallback(InputState.Committing(poppedText: " "))
stateCallback(InputState.Committing(textToCommit: " "))
stateCallback(InputState.Empty())
} else if ifLangModelHasUnigrams(forKey: " ") {
insertToCompositorAtCursor(reading: " ")
let poppedText = popOverflowComposingTextAndWalk
let textToCommit = popOverflowComposingTextAndWalk
let inputting = buildInputtingState
inputting.poppedText = poppedText
inputting.textToCommit = textToCommit
stateCallback(inputting)
}
return true
@ -371,9 +383,9 @@ extension KeyHandler {
if ifLangModelHasUnigrams(forKey: "_punctuation_list") {
if composer.isEmpty {
insertToCompositorAtCursor(reading: "_punctuation_list")
let poppedText: String! = popOverflowComposingTextAndWalk
let textToCommit: String! = popOverflowComposingTextAndWalk
let inputting = buildInputtingState
inputting.poppedText = poppedText
inputting.textToCommit = textToCommit
stateCallback(inputting)
stateCallback(buildCandidate(state: inputting, isTypingVertical: input.isTypingVertical))
} else { // If there is still unfinished bpmf reading, ignore the punctuation
@ -394,7 +406,9 @@ extension KeyHandler {
// MARK: Punctuation
// If nothing is matched, see if it's a punctuation key for current layout.
///
/// - /
/// -
var punctuationNamePrefix = ""
@ -425,7 +439,8 @@ extension KeyHandler {
return true
}
// if nothing is matched, see if it's a punctuation key.
///
let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))]
let punctuation: String = arrPunctuations.joined(separator: "")
@ -453,13 +468,12 @@ extension KeyHandler {
}
}
// MARK: - Still Nothing.
// MARK: - (Still Nothing)
// Still nothing? Then we update the composing buffer.
// Note that some app has strange behavior if we don't do this,
// "thinking" that the key is not actually consumed.
// F1-F12
// 便
/// ctlInputMethod
///
/// F1-F12
/// 便
if (state is InputState.NotEmpty) || !composer.isEmpty {
IME.prtDebugIntel(
"Blocked data: charCode: \(charCode), keyCode: \(input.keyCode)")

View File

@ -24,35 +24,35 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// 調調
import Cocoa
// MARK: - § State managements.
// MARK: - § 調 (Functions Interact With States).
extension KeyHandler {
// MARK: - State Building
///
var buildInputtingState: InputState.Inputting {
// "Updating the composing buffer" means to request the client
// to "refresh" the text input buffer with our "composing text"
/// (Update the composing buffer)
/// NSAttributeString
var tooltipParameterRef: [String] = ["", ""]
var composingBuffer = ""
var composedStringCursorIndex = 0
var readingCursorIndex = 0
// We must do some Unicode codepoint counting to find the actual cursor location for the client
// i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars
// locations. Since we are using Swift, we use .utf16 as the equivalent of NSString.length().
/// IMK UTF8 emoji
/// Swift.utf16NSString.length()
///
for walkedNode in walkedAnchors {
if let theNode = walkedNode.node {
let strNodeValue = theNode.currentKeyValue.value
composingBuffer += strNodeValue
let arrSplit: [String] = Array(strNodeValue).map { String($0) }
let codepointCount = arrSplit.count
// This re-aligns the cursor index in the composed string
// (the actual cursor on the screen) with the compositor's logical
// cursor (reading) cursor; each built node has a "spanning length"
// (e.g. two reading blocks has a spanning length of 2), and we
// accumulate those lengths to calculate the displayed cursor
// index.
///
/// NodeAnchorspanningLength
///
let spanningLength: Int = walkedNode.spanningLength
if readingCursorIndex + spanningLength <= compositorCursorIndex {
composedStringCursorIndex += strNodeValue.utf16.count
@ -69,14 +69,12 @@ extension KeyHandler {
if readingCursorIndex < compositorCursorIndex {
composedStringCursorIndex += strNodeValue.utf16.count
readingCursorIndex += spanningLength
if readingCursorIndex > compositorCursorIndex {
readingCursorIndex = compositorCursorIndex
}
// Now we start preparing the contents of the tooltips used
// in cases of moving cursors across certain emojis which emoji
// char count is inequal to the reading count.
// Example in McBopomofo: Typing (3 readings) gets a tree emoji.
// Example in vChewing: Typing (2 readings) gets a pasta emoji.
readingCursorIndex = min(readingCursorIndex, compositorCursorIndex)
///
///
///
///
///
switch compositorCursorIndex {
case compositor.readings.count...:
tooltipParameterRef[0] = compositor.readings[compositor.readings.count - 1]
@ -94,9 +92,8 @@ extension KeyHandler {
}
}
// Now, we gather all the intel, separate the composing buffer to two parts (head and tail),
// and insert the reading text (the Mandarin syllable) in between them.
// The reading text is what the user is typing.
///
/// 便 composer
var arrHead = [String.UTF16View.Element]()
var arrTail = [String.UTF16View.Element]()
@ -108,34 +105,38 @@ extension KeyHandler {
}
}
/// stringview
///
let head = String(utf16CodeUnits: arrHead, count: arrHead.count)
let reading = composer.getInlineCompositionForIMK(isHanyuPinyin: mgrPrefs.showHanyuPinyinInCompositionBuffer)
let tail = String(utf16CodeUnits: arrTail, count: arrTail.count)
let composedText = head + reading + tail
let cursorIndex = composedStringCursorIndex + reading.utf16.count
/// 使
let stateResult = InputState.Inputting(composingBuffer: composedText, cursorIndex: cursorIndex)
// Now we start weaving the contents of the tooltip.
if tooltipParameterRef[0].isEmpty, tooltipParameterRef[1].isEmpty {
stateResult.tooltip = ""
} else if tooltipParameterRef[0].isEmpty {
stateResult.tooltip = String(
format: NSLocalizedString("Cursor is to the rear of \"%@\".", comment: ""),
tooltipParameterRef[1]
)
} else if tooltipParameterRef[1].isEmpty {
stateResult.tooltip = String(
format: NSLocalizedString("Cursor is in front of \"%@\".", comment: ""),
tooltipParameterRef[0]
)
} else {
stateResult.tooltip = String(
format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""),
tooltipParameterRef[0], tooltipParameterRef[1]
)
///
switch (tooltipParameterRef[0].isEmpty, tooltipParameterRef[1].isEmpty) {
case (true, true): stateResult.tooltip.removeAll()
case (true, false):
stateResult.tooltip = String(
format: NSLocalizedString("Cursor is to the rear of \"%@\".", comment: ""),
tooltipParameterRef[1]
)
case (false, true):
stateResult.tooltip = String(
format: NSLocalizedString("Cursor is in front of \"%@\".", comment: ""),
tooltipParameterRef[0]
)
case (false, false):
stateResult.tooltip = String(
format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""),
tooltipParameterRef[0], tooltipParameterRef[1]
)
}
///
if !stateResult.tooltip.isEmpty {
ctlInputMethod.tooltipController.setColor(state: .denialOverflow)
}
@ -145,6 +146,11 @@ extension KeyHandler {
// MARK: -
///
/// - Parameters:
/// - currentState:
/// - isTypingVertical:
/// - Returns:
func buildCandidate(
state currentState: InputState.NotEmpty,
isTypingVertical: Bool = false
@ -159,13 +165,19 @@ extension KeyHandler {
// MARK: -
// buildAssociatePhraseStateWithKey
// 使
// Core buildAssociatePhraseArray
// String Swift
// nil
//
//
///
///
/// buildAssociatePhraseStateWithKey
/// 使
/// Core buildAssociatePhraseArray
/// String Swift
/// nil
///
///
/// - Parameters:
/// - key:
/// - isTypingVertical:
/// - Returns:
func buildAssociatePhraseState(
withKey key: String!,
isTypingVertical: Bool
@ -178,6 +190,13 @@ extension KeyHandler {
// MARK: -
///
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleMarkingState(
_ state: InputState.Marking,
input: InputSignal,
@ -246,8 +265,16 @@ extension KeyHandler {
return false
}
// MARK: -
// MARK: -
///
/// - Parameters:
/// - customPunctuation:
/// - state:
/// - isTypingVertical:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handlePunctuation(
_ customPunctuation: String,
state: InputState,
@ -261,9 +288,9 @@ extension KeyHandler {
if composer.isEmpty {
insertToCompositorAtCursor(reading: customPunctuation)
let poppedText = popOverflowComposingTextAndWalk
let textToCommit = popOverflowComposingTextAndWalk
let inputting = buildInputtingState
inputting.poppedText = poppedText
inputting.textToCommit = textToCommit
stateCallback(inputting)
if mgrPrefs.useSCPCTypingMode, composer.isEmpty {
@ -273,8 +300,8 @@ extension KeyHandler {
)
if candidateState.candidates.count == 1 {
clear()
if let strPoppedText: String = candidateState.candidates.first {
stateCallback(InputState.Committing(poppedText: strPoppedText) as InputState.Committing)
if let strtextToCommit: String = candidateState.candidates.first {
stateCallback(InputState.Committing(textToCommit: strtextToCommit) as InputState.Committing)
stateCallback(InputState.Empty())
} else {
stateCallback(candidateState)
@ -293,8 +320,13 @@ extension KeyHandler {
}
}
// MARK: - Enter
// MARK: - Enter
/// Enter
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleEnter(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -303,13 +335,18 @@ extension KeyHandler {
guard let currentState = state as? InputState.Inputting else { return false }
clear()
stateCallback(InputState.Committing(poppedText: currentState.composingBuffer))
stateCallback(InputState.Committing(textToCommit: currentState.composingBuffer))
stateCallback(InputState.Empty())
return true
}
// MARK: - CMD+Enter
// MARK: - CMD+Enter
/// CMD+Enter
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleCtrlCommandEnter(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -329,13 +366,18 @@ extension KeyHandler {
clear()
stateCallback(InputState.Committing(poppedText: composingBuffer))
stateCallback(InputState.Committing(textToCommit: composingBuffer))
stateCallback(InputState.Empty())
return true
}
// MARK: - CMD+Alt+Enter Ruby
// MARK: - CMD+Alt+Enter Ruby
/// CMD+Alt+Enter Ruby
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleCtrlOptionCommandEnter(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -368,13 +410,19 @@ extension KeyHandler {
clear()
stateCallback(InputState.Committing(poppedText: composed))
stateCallback(InputState.Committing(textToCommit: composed))
stateCallback(InputState.Empty())
return true
}
// MARK: - Backspace (macOS Delete)
/// Backspace (macOS Delete)
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleBackspace(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -386,7 +434,7 @@ extension KeyHandler {
composer.clear()
} else if composer.isEmpty {
if compositorCursorIndex >= 0 {
deleteBuilderReadingInFrontOfCursor()
deleteCompositorReadingAtTheRearOfCursor()
walk()
} else {
IME.prtDebugIntel("9D69908D")
@ -408,6 +456,12 @@ extension KeyHandler {
// MARK: - PC Delete (macOS Fn+BackSpace)
/// PC Delete (macOS Fn+BackSpace)
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleDelete(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -417,7 +471,7 @@ extension KeyHandler {
if composer.isEmpty {
if compositorCursorIndex != compositorLength {
deleteBuilderReadingToTheFrontOfCursor()
deleteCompositorReadingToTheFrontOfCursor()
walk()
let inputting = buildInputtingState
// count > 0!isEmpty滿
@ -442,6 +496,12 @@ extension KeyHandler {
// MARK: - 90
/// 90
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleAbsorbedArrowKey(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -456,8 +516,14 @@ extension KeyHandler {
return true
}
// MARK: - Home
// MARK: - Home
/// Home
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleHome(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -484,8 +550,14 @@ extension KeyHandler {
return true
}
// MARK: - End
// MARK: - End
/// End
/// - Parameters:
/// - state:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleEnd(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -512,8 +584,13 @@ extension KeyHandler {
return true
}
// MARK: - Esc
// MARK: - Esc
/// Esc
/// - Parameters:
/// - state:
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleEsc(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
@ -521,17 +598,13 @@ extension KeyHandler {
) -> Bool {
guard state is InputState.Inputting else { return false }
let escToClearInputBufferEnabled: Bool = mgrPrefs.escToCleanInputBuffer
if escToClearInputBufferEnabled {
// If the option is enabled, we clear everything in the buffer.
// This includes walked nodes and the reading. Note that this convention
// is by default in macOS 10.0-10.5 built-in Panasonic Hanin and later macOS Zhuyin.
// Some Windows users hate this design, hence the option here to disable it.
if mgrPrefs.escToCleanInputBuffer {
///
/// macOS Windows 使
clear()
stateCallback(InputState.EmptyIgnoringPreviousState())
} else {
// If reading is not empty, we cancel the reading.
///
if !composer.isEmpty {
composer.clear()
if compositorLength == 0 {
@ -546,6 +619,13 @@ extension KeyHandler {
// MARK: -
///
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleForward(
state: InputState,
input: InputSignal,
@ -595,6 +675,13 @@ extension KeyHandler {
// MARK: -
///
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleBackward(
state: InputState,
input: InputSignal,
@ -644,6 +731,13 @@ extension KeyHandler {
// MARK: - Tab Shift+Space
///
/// - Parameters:
/// - state:
/// - reverseModifier:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleInlineCandidateRotation(
state: InputState,
reverseModifier: Bool,
@ -697,18 +791,17 @@ extension KeyHandler {
var currentIndex = 0
if currentNode.score < currentNode.kSelectedCandidateScore {
// Once the user never select a candidate for the node,
// we start from the first candidate, so the user has a
// chance to use the unigram with two or more characters
// when type the tab key for the first time.
//
// In other words, if a user type two BPMF readings,
// but the score of seeing them as two unigrams is higher
// than a phrase with two characters, the user can just
// use the longer phrase by tapping the tab key.
/// 使
/// 使
/// 2 使
///
///
/// 使
/// (Shift+)Tab ()
/// Shift(+CMD)+Space Tab
if candidates[0] == currentValue {
// If the first candidate is the value of the
// current node, we use next one.
///
///
if reverseModifier {
currentIndex = candidates.count - 1
} else {

View File

@ -178,7 +178,7 @@ class ctlInputMethod: IMKInputController {
override func commitComposition(_ sender: Any!) {
_ = sender // Stop clang-format from ruining the parameters of this function.
if let state = state as? InputState.NotEmpty {
handle(state: InputState.Committing(poppedText: state.composingBuffer))
handle(state: InputState.Committing(textToCommit: state.composingBuffer))
}
resetKeyHandler()
}
@ -306,9 +306,9 @@ extension ctlInputMethod {
ctlCandidateCurrent.visible = false
hideTooltip()
let poppedText = state.poppedText
if !poppedText.isEmpty {
commit(text: poppedText)
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
client().setMarkedText(
"", selectionRange: NSRange(location: 0, length: 0),
@ -321,9 +321,9 @@ extension ctlInputMethod {
ctlCandidateCurrent.visible = false
hideTooltip()
let poppedText = state.poppedText
if !poppedText.isEmpty {
commit(text: poppedText)
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
@ -622,7 +622,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
state: .SymbolTable(node: node, isTypingVertical: state.isTypingVertical)
)
} else {
handle(state: .Committing(poppedText: node.title))
handle(state: .Committing(textToCommit: node.title))
handle(state: .Empty())
}
return
@ -637,7 +637,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
if mgrPrefs.useSCPCTypingMode {
keyHandler.clear()
let composingBuffer = inputting.composingBuffer
handle(state: .Committing(poppedText: composingBuffer))
handle(state: .Committing(textToCommit: composingBuffer))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: composingBuffer, isTypingVertical: state.isTypingVertical
@ -655,7 +655,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
if let state = state as? InputState.AssociatedPhrases {
let selectedValue = state.candidates[index]
handle(state: .Committing(poppedText: selectedValue))
handle(state: .Committing(textToCommit: selectedValue))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: selectedValue, isTypingVertical: state.isTypingVertical