Pre Merge pull request !76 from ShikiSuen/upd/1.9.0sp1

This commit is contained in:
ShikiSuen 2022-08-09 07:37:01 +00:00 committed by Gitee
commit 8312addde0
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
49 changed files with 1847 additions and 1844 deletions

View File

@ -32,8 +32,8 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in
Although there is no Lukhnos's codes left in the current repository, we still credit him for his previous work:
- Lukhnos Liu:
- Developer of Gramambular language engine (removed since vChewing 1.5.4).
- Shiki Suen's Megrez engine is basically a Swift-rewritten version of Gramambular with further development.
- Developer of Gramambular 2 language engine (removed since vChewing 1.5.4).
- Shiki Suen's Megrez engine (MIT License) is basically a Swift-rewritten version of Gramambular 2 with further development.
- Developer of Mandarin syllable composer (removed since vChewing 1.5.7).
- Shiki Suen's Tekkon engine is made from scratch and has no relationship to Mandarin syllable composer.

View File

@ -277,7 +277,7 @@ public enum InputState {
return arrOutput.joined(separator: " ")
}
private var deleteTargetExists = false
private var markedTargetExists = false
var tooltip: String {
if composingBuffer.count != readings.count {
ctlInputMethod.tooltipController.setColor(state: .denialOverflow)
@ -318,11 +318,12 @@ public enum InputState {
userPhrase: text, mode: IME.currentInputMode, key: joined
)
if exist {
deleteTargetExists = exist
markedTargetExists = exist
ctlInputMethod.tooltipController.setColor(state: .prompt)
return String(
format: NSLocalizedString(
"\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude.", comment: ""
"\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude.",
comment: ""
) + "\n// " + literalReadingThread, text
)
}
@ -397,12 +398,10 @@ public enum InputState {
return state
}
var validToWrite: Bool {
var validToFilter: Bool {
///
/// deleteTargetExists 使
(ctlInputMethod.areWeDeleting && !deleteTargetExists)
? false
: allowedMarkRange.contains(literalMarkedRange.count)
markedTargetExists ? allowedMarkRange.contains(literalMarkedRange.count) : false
}
var chkIfUserPhraseExists: Bool {
@ -418,7 +417,8 @@ public enum InputState {
let text = composingBuffer.utf16SubString(with: markedRange)
let selectedReadings = readings[literalMarkedRange]
let joined = selectedReadings.joined(separator: "-")
return "\(text) \(joined)"
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
return "\(text) \(joined)\(nerfedScore)"
}
var userPhraseConverted: String {
@ -426,8 +426,9 @@ public enum InputState {
ChineseConverter.crossConvert(composingBuffer.utf16SubString(with: markedRange)) ?? ""
let selectedReadings = readings[literalMarkedRange]
let joined = selectedReadings.joined(separator: "-")
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
let convertedMark = "#𝙃𝙪𝙢𝙖𝙣𝘾𝙝𝙚𝙘𝙠𝙍𝙚𝙦𝙪𝙞𝙧𝙚𝙙"
return "\(text) \(joined)\t\(convertedMark)"
return "\(text) \(joined)\(nerfedScore)\t\(convertedMark)"
}
}

View File

@ -23,7 +23,7 @@ protocol KeyHandlerDelegate {
_: KeyHandler, didSelectCandidateAt index: Int,
ctlCandidate controller: ctlCandidateProtocol
)
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol)
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol, addToFilter: Bool)
-> Bool
}
@ -38,13 +38,10 @@ public class KeyHandler {
///
var isTypingContentEmpty: Bool { composer.isEmpty && compositor.isEmpty }
///
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] { compositor.walkedAnchors } //
/// (ctlInputMethod)便
var delegate: KeyHandlerDelegate?
@ -72,7 +69,7 @@ public class KeyHandler {
///
public init() {
/// ensureCompositor()
compositor = Megrez.Compositor(lm: currentLM, separator: "-")
compositor = Megrez.Compositor(with: currentLM, separator: "-")
///
ensureParser()
/// inputMode
@ -91,7 +88,8 @@ public class KeyHandler {
///
/// 使 Node Crossing
var actualCandidateCursor: Int {
mgrPrefs.useRearCursorMode ? min(compositor.cursor, compositor.length - 1) : max(compositor.cursor, 1)
compositor.cursor
- ((compositor.cursor == compositor.width || !mgrPrefs.useRearCursorMode) && compositor.cursor > 0 ? 1 : 0)
}
///
@ -116,23 +114,6 @@ public class KeyHandler {
}
}
///
///
/// Viterbi 使 O(N^2)
/// 使
/// 使
///
var commitOverflownCompositionAndWalk: String {
var textToCommit = ""
if compositor.width > mgrPrefs.composingBufferSize, !walkedAnchors.isEmpty {
let anchor: Megrez.NodeAnchor = walkedAnchors[0]
textToCommit = anchor.node.currentPair.value
compositor.removeHeadReadings(count: anchor.spanLength)
}
walk()
return textToCommit
}
///
/// - Parameter key:
/// - Returns:
@ -145,144 +126,115 @@ public class KeyHandler {
return arrResult
}
///
///
///
/// - Parameters:
/// - value:
/// - value:
/// - respectCursorPushing: true
func fixNode(candidate: (String, String), respectCursorPushing: Bool = true) {
let theCandidate: Megrez.KeyValuePaired = .init(key: candidate.0, value: candidate.1)
let adjustedCursor = max(0, min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositor.length))
//
let selectedNode: Megrez.NodeAnchor = compositor.fixNodeWithCandidate(theCandidate, at: adjustedCursor)
//
if !mgrPrefs.useSCPCTypingMode {
var addToUserOverrideModel = true
//
if selectedNode.spanLength != theCandidate.value.count {
IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
addToUserOverrideModel = false
}
if addToUserOverrideModel {
// SymbolLM Score -12
if selectedNode.node.scoreForPaired(candidate: theCandidate) <= -12 {
IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
addToUserOverrideModel = false
}
}
if addToUserOverrideModel, mgrPrefs.fetchSuggestionsFromUserOverrideModel {
IME.prtDebugIntel("UOM: Start Observation.")
// 使
//
// AppDelegate
mgrPrefs.failureFlagForUOMObservation = true
//
//
currentUOM.observe(
walkedAnchors: walkedAnchors, cursorIndex: adjustedCursor, candidate: theCandidate.value,
timestamp: NSDate().timeIntervalSince1970, saveCallback: { mgrLangModel.saveUserOverrideModelData() }
)
//
mgrPrefs.failureFlagForUOMObservation = false
}
}
let actualCursor = actualCandidateCursor
let theCandidate: Megrez.Compositor.Candidate = .init(key: candidate.0, value: candidate.1)
if !compositor.overrideCandidate(theCandidate, at: actualCursor, overrideType: .withHighScore) { return }
let previousWalk = compositor.walkedNodes
//
walk()
let currentWalk = compositor.walkedNodes
// 使
var accumulatedCursor = 0
let currentNode = currentWalk.findNode(at: actualCandidateCursor, target: &accumulatedCursor)
guard let currentNode = currentNode else { return }
if currentNode.currentUnigram.score > -12, mgrPrefs.fetchSuggestionsFromUserOverrideModel {
IME.prtDebugIntel("UOM: Start Observation.")
// 使
//
// AppDelegate
mgrPrefs.failureFlagForUOMObservation = true
//
//
currentUOM.performObservation(
walkedBefore: previousWalk, walkedAfter: currentWalk, cursor: actualCandidateCursor,
timestamp: Date().timeIntervalSince1970, saveCallback: { mgrLangModel.saveUserOverrideModelData() }
)
//
mgrPrefs.failureFlagForUOMObservation = false
}
///
if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing {
// compositor.cursor = accumulatedCursor
compositor.jumpCursorBySpan(to: .front)
}
}
///
func markNodesFixedIfNecessary() {
let width = compositor.width
if width <= kMaxComposingBufferNeedsToWalkSize {
return
}
var index = 0
for anchor in walkedAnchors {
if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
if anchor.node.score < Megrez.Node.kSelectedCandidateScore {
compositor.fixNodeWithCandidate(anchor.node.currentPair, at: index + anchor.spanLength)
}
index += anchor.spanLength
}
}
///
func getCandidatesArray(fixOrder: Bool = true) -> [(String, String)] {
var arrAnchors: [Megrez.NodeAnchor] = rawAnchorsOfNodes
var arrCandidates: [Megrez.KeyValuePaired] = .init()
/// 使 nodesCrossing macOS
/// nodeCrossing
var arrCandidates: [Megrez.Compositor.Candidate] = {
switch mgrPrefs.useRearCursorMode {
case false:
return compositor.fetchCandidates(at: actualCandidateCursor, filter: .endAt)
case true:
return compositor.fetchCandidates(at: actualCandidateCursor, filter: .beginAt)
}
}()
/// nodes
///
///
if arrAnchors.isEmpty { return .init() }
if arrCandidates.isEmpty { return .init() }
//
arrAnchors = arrAnchors.stableSort { $0.spanLength > $1.spanLength }
//
for currentCandidate in arrAnchors.map(\.node.candidates).joined() {
// / JIS
//
//
arrCandidates.append(currentCandidate)
}
// 調
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel || mgrPrefs.useSCPCTypingMode || fixOrder {
return arrCandidates.map { ($0.key, $0.value) }
}
let arrSuggestedUnigrams: [Megrez.Unigram] = fetchSuggestedCandidates().stableSort { $0.score > $1.score }
let arrSuggestedCandidates: [Megrez.KeyValuePaired] = arrSuggestedUnigrams.map(\.keyValue)
let arrSuggestedUnigrams: [(String, Megrez.Unigram)] = fetchSuggestionsFromUOM(apply: false)
let arrSuggestedCandidates: [Megrez.Compositor.Candidate] = arrSuggestedUnigrams.map {
Megrez.Compositor.Candidate(key: $0.0, value: $0.1.value)
}
arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates
arrCandidates = arrCandidates.deduplicate
arrCandidates = arrCandidates.stableSort { $0.key.split(separator: "-").count > $1.key.split(separator: "-").count }
return arrCandidates.map { ($0.key, $0.value) }
}
///
func fetchSuggestedCandidates() -> [Megrez.Unigram] {
currentUOM.suggest(
walkedAnchors: walkedAnchors, cursorIndex: compositor.cursor,
timestamp: NSDate().timeIntervalSince1970
)
}
///
func fetchAndApplySuggestionsFromUserOverrideModel() {
@discardableResult func fetchSuggestionsFromUOM(apply: Bool) -> [(String, Megrez.Unigram)] {
var arrResult = [(String, Megrez.Unigram)]()
///
if mgrPrefs.useSCPCTypingMode { return }
if mgrPrefs.useSCPCTypingMode { return arrResult }
///
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel { return }
///
let overrideValue = fetchSuggestedCandidates().first?.keyValue.value ?? ""
///
if !overrideValue.isEmpty {
IME.prtDebugIntel(
"UOM: Suggestion retrieved, overriding the node score of the selected candidate.")
compositor.overrideNodeScoreForSelectedCandidate(
location: min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositor.length),
value: overrideValue,
overridingScore: findHighestScore(nodeAnchors: rawAnchorsOfNodes, epsilon: kEpsilon)
)
} else {
IME.prtDebugIntel("UOM: Blank suggestion retrieved, dismissing.")
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel { return arrResult }
///
let suggestion = currentUOM.fetchSuggestion(
currentWalk: compositor.walkedNodes, cursor: actualCandidateCursor, timestamp: Date().timeIntervalSince1970
)
arrResult.append(contentsOf: suggestion.candidates)
if apply {
///
if !suggestion.isEmpty, let newestSuggestedCandidate = suggestion.candidates.last {
let overrideBehavior: Megrez.Compositor.Node.OverrideType =
suggestion.forceHighScoreOverride ? .withHighScore : .withTopUnigramScore
let suggestedPair: Megrez.Compositor.Candidate = .init(
key: newestSuggestedCandidate.0, value: newestSuggestedCandidate.1.value
)
IME.prtDebugIntel(
"UOM: Suggestion retrieved, overriding the node score of the selected candidate: \(suggestedPair.toNGramKey)")
if !compositor.overrideCandidate(suggestedPair, at: actualCandidateCursor, overrideType: overrideBehavior) {
compositor.overrideCandidateLiteral(
newestSuggestedCandidate.1.value, at: actualCandidateCursor, overrideType: overrideBehavior
)
}
walk()
} else {
IME.prtDebugIntel("UOM: Blank suggestion retrieved, dismissing.")
}
}
}
///
/// - Parameters:
/// - nodes:
/// - epsilon:
/// - Returns:
func findHighestScore(nodeAnchors: [Megrez.NodeAnchor], epsilon: Double) -> Double {
nodeAnchors.map(\.node.highestUnigramScore).max() ?? 0 + epsilon
arrResult = arrResult.stableSort { $0.1.score > $1.1.score }
return arrResult
}
// MARK: - Extracted methods and functions (Tekkon).
@ -335,15 +287,6 @@ public class KeyHandler {
// MARK: - Extracted methods and functions (Megrez).
///
var rawAnchorsOfNodes: [Megrez.NodeAnchor] {
/// 使 nodesCrossing macOS
/// nodeCrossing
mgrPrefs.useRearCursorMode
? compositor.nodesBeginningAt(location: actualCandidateCursor)
: compositor.nodesEndingAt(location: actualCandidateCursor)
}
///
func syncBaseLMPrefs() {
currentLM.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled
@ -354,7 +297,7 @@ public class KeyHandler {
/// 使
func ensureCompositor() {
// 西
compositor = Megrez.Compositor(lm: currentLM, separator: "-")
compositor = Megrez.Compositor(with: currentLM, separator: "-")
}
///

View File

@ -51,7 +51,6 @@ extension KeyHandler {
//
// 使 BackSpace
// compositor.isEmpty
clear()
stateCallback(InputState.EmptyIgnoringPreviousState())
stateCallback(InputState.Empty())
} else {
@ -67,7 +66,6 @@ extension KeyHandler {
if input.isEnter {
if state is InputState.AssociatedPhrases, !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter {
clear()
stateCallback(InputState.EmptyIgnoringPreviousState())
stateCallback(InputState.Empty())
return true
@ -309,13 +307,15 @@ extension KeyHandler {
let punctuationNamePrefix: String = generatePunctuationNamePrefix(withKeyCondition: input)
let parser = currentMandarinParser
let arrCustomPunctuations: [String] = [
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),
punctuationNamePrefix, parser, String(format: "%c", charCode.isPrintableASCII ? CChar(charCode) : inputText),
]
let customPunctuation: String = arrCustomPunctuations.joined(separator: "")
///
let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))]
let arrPunctuations: [String] = [
punctuationNamePrefix, String(format: "%c", charCode.isPrintableASCII ? CChar(charCode) : inputText),
]
let punctuation: String = arrPunctuations.joined(separator: "")
var shouldAutoSelectCandidate: Bool =
@ -323,7 +323,9 @@ extension KeyHandler {
|| currentLM.hasUnigramsFor(key: punctuation)
if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey {
let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode))
let letter: String! = String(
format: "%@%c", "_letter_", charCode.isPrintableASCII ? CChar(charCode) : inputText
)
if currentLM.hasUnigramsFor(key: letter) { shouldAutoSelectCandidate = true }
}
@ -335,7 +337,6 @@ extension KeyHandler {
didSelectCandidateAt: candidateIndex,
ctlCandidate: ctlCandidateCurrent
)
clear()
stateCallback(InputState.EmptyIgnoringPreviousState())
stateCallback(InputState.Empty())
return handle(

View File

@ -81,23 +81,19 @@ extension KeyHandler {
}
//
compositor.insertReading(readingKey)
compositor.insertKey(readingKey)
//
let textToCommit = commitOverflownCompositionAndWalk
walk()
//
fetchAndApplySuggestionsFromUserOverrideModel()
//
markNodesFixedIfNecessary()
fetchSuggestionsFromUOM(apply: true)
//
composer.clear()
// updateClientComposingBuffer()
let inputting = buildInputtingState
inputting.textToCommit = textToCommit
stateCallback(inputting)
///
@ -106,10 +102,9 @@ extension KeyHandler {
state: inputting,
isTypingVertical: input.isTypingVertical
)
if choosingCandidates.candidates.count == 1 {
clear()
let reading: String = choosingCandidates.candidates.first?.0 ?? ""
let text: String = choosingCandidates.candidates.first?.1 ?? ""
if choosingCandidates.candidates.count == 1, let firstCandidate = choosingCandidates.candidates.first {
let reading: String = firstCandidate.0
let text: String = firstCandidate.1
stateCallback(InputState.Committing(textToCommit: text))
if !mgrPrefs.associatedPhrasesEnabled {

View File

@ -69,7 +69,6 @@ extension KeyHandler {
// BackSpace
} else if input.isCapsLockOn || input.isASCIIModeInput {
//
clear()
stateCallback(InputState.Empty())
// Shift
@ -99,7 +98,6 @@ extension KeyHandler {
if !(state is InputState.ChoosingCandidate || state is InputState.AssociatedPhrases
|| state is InputState.SymbolTable)
{
clear()
stateCallback(InputState.Empty())
stateCallback(InputState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty())
@ -162,14 +160,12 @@ extension KeyHandler {
if !composingBuffer.isEmpty {
stateCallback(InputState.Committing(textToCommit: composingBuffer))
}
clear()
stateCallback(InputState.Committing(textToCommit: " "))
stateCallback(InputState.Empty())
} else if currentLM.hasUnigramsFor(key: " ") {
compositor.insertReading(" ")
let textToCommit = commitOverflownCompositionAndWalk
compositor.insertKey(" ")
walk()
let inputting = buildInputtingState
inputting.textToCommit = textToCommit
stateCallback(inputting)
}
return true
@ -259,13 +255,13 @@ extension KeyHandler {
// MARK: Backspace
if input.isBackSpace {
return handleBackSpace(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
return handleBackSpace(state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback)
}
// MARK: Delete
if input.isDelete || input.emacsKey == EmacsKey.delete {
return handleDelete(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
return handleDelete(state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback)
}
// MARK: Enter
@ -286,10 +282,9 @@ extension KeyHandler {
if input.isOptionHold {
if currentLM.hasUnigramsFor(key: "_punctuation_list") {
if composer.isEmpty {
compositor.insertReading("_punctuation_list")
let textToCommit: String! = commitOverflownCompositionAndWalk
compositor.insertKey("_punctuation_list")
walk()
let inputting = buildInputtingState
inputting.textToCommit = textToCommit
stateCallback(inputting)
stateCallback(buildCandidate(state: inputting, isTypingVertical: input.isTypingVertical))
} else { //
@ -334,7 +329,7 @@ extension KeyHandler {
let punctuationNamePrefix: String = generatePunctuationNamePrefix(withKeyCondition: input)
let parser = currentMandarinParser
let arrCustomPunctuations: [String] = [
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),
punctuationNamePrefix, parser, String(format: "%c", charCode.isPrintableASCII ? CChar(charCode) : inputText),
]
let customPunctuation: String = arrCustomPunctuations.joined(separator: "")
if handlePunctuation(
@ -349,7 +344,9 @@ extension KeyHandler {
///
let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))]
let arrPunctuations: [String] = [
punctuationNamePrefix, String(format: "%c", charCode.isPrintableASCII ? CChar(charCode) : inputText),
]
let punctuation: String = arrPunctuations.joined(separator: "")
if handlePunctuation(
@ -362,20 +359,6 @@ extension KeyHandler {
return true
}
// 使 2.2
if input.isUpperCaseASCIILetterKey {
let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode))
if handlePunctuation(
letter,
state: state,
usingVerticalTyping: input.isTypingVertical,
stateCallback: stateCallback,
errorCallback: errorCallback
) {
return true
}
}
// MARK: / (Full-Width / Half-Width Space)
/// 使
@ -387,6 +370,38 @@ extension KeyHandler {
}
}
// MARK: Shift+ (Shift+Letter Processing)
// 使 2.2
if input.isUpperCaseASCIILetterKey, !input.isCommandHold, !input.isControlHold {
if input.isShiftHold { // isOptionHold
switch mgrPrefs.upperCaseLetterKeyBehavior {
case 1:
stateCallback(InputState.Empty())
stateCallback(InputState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty())
return true
case 2:
stateCallback(InputState.Empty())
stateCallback(InputState.Committing(textToCommit: inputText.uppercased()))
stateCallback(InputState.Empty())
return true
default: // case 0
let letter: String! = String(
format: "%@%c", "_letter_", charCode.isPrintableASCII ? CChar(charCode) : inputText)
if handlePunctuation(
letter,
state: state,
usingVerticalTyping: input.isTypingVertical,
stateCallback: stateCallback,
errorCallback: errorCallback
) {
return true
}
}
}
}
// MARK: - (Still Nothing)
/// ctlInputMethod

View File

@ -22,21 +22,20 @@ extension KeyHandler {
/// (Update the composing buffer)
/// NSAttributeString
var tooltipParameterRef: [String] = ["", ""]
let nodeValuesArray: [String] = walkedAnchors.values
let nodeValuesArray: [String] = compositor.walkedNodes.values
var composedStringCursorIndex = 0
var readingCursorIndex = 0
/// IMK UTF8 emoji
/// Swift.utf16NSString.length()
///
for theAnchor in walkedAnchors {
let theNode = theAnchor.node
let strNodeValue = theNode.currentPair.value
for theNode in compositor.walkedNodes {
let strNodeValue = theNode.value
let arrSplit: [String] = Array(strNodeValue).map { String($0) }
let codepointCount = arrSplit.count
///
/// NodeAnchorspanningLength
///
let spanningLength: Int = theAnchor.spanLength
let spanningLength: Int = theNode.spanLength
if readingCursorIndex + spanningLength <= compositor.cursor {
composedStringCursorIndex += strNodeValue.utf16.count
readingCursorIndex += spanningLength
@ -60,14 +59,14 @@ extension KeyHandler {
///
///
switch compositor.cursor {
case compositor.readings.count...:
case compositor.keys.count...:
// compositor.cursor readings.count Megrez
tooltipParameterRef[0] = compositor.readings[compositor.cursor - 1]
tooltipParameterRef[0] = compositor.keys[compositor.cursor - 1]
case 0:
tooltipParameterRef[1] = compositor.readings[compositor.cursor]
tooltipParameterRef[1] = compositor.keys[compositor.cursor]
default:
tooltipParameterRef[0] = compositor.readings[compositor.cursor - 1]
tooltipParameterRef[1] = compositor.readings[compositor.cursor]
tooltipParameterRef[0] = compositor.keys[compositor.cursor - 1]
tooltipParameterRef[1] = compositor.keys[compositor.cursor]
}
}
@ -125,7 +124,7 @@ extension KeyHandler {
cursorIndex: currentState.cursorIndex,
candidates: getCandidatesArray(fixOrder: mgrPrefs.useFixecCandidateOrderOnSelection),
isTypingVertical: isTypingVertical,
nodeValuesArray: walkedAnchors.values
nodeValuesArray: compositor.walkedNodes.values
)
}
@ -177,7 +176,12 @@ extension KeyHandler {
// Enter
if input.isEnter {
if let keyHandlerDelegate = delegate {
if !keyHandlerDelegate.keyHandler(self, didRequestWriteUserPhraseWith: state) {
//
if input.isShiftHold, input.isCommandHold, !state.validToFilter {
IME.prtDebugIntel("2EAC1F7A")
errorCallback()
return true
} else if !keyHandlerDelegate.keyHandler(self, didRequestWriteUserPhraseWith: state, addToFilter: false) {
IME.prtDebugIntel("5B69CC8D")
errorCallback()
return true
@ -187,6 +191,24 @@ extension KeyHandler {
return true
}
// BackSpace & Delete
if input.isBackSpace || input.isDelete {
if let keyHandlerDelegate = delegate {
if !state.validToFilter {
IME.prtDebugIntel("1F88B191")
errorCallback()
return true
}
if !keyHandlerDelegate.keyHandler(self, didRequestWriteUserPhraseWith: state, addToFilter: true) {
IME.prtDebugIntel("68D3C6C8")
errorCallback()
return true
}
}
stateCallback(buildInputtingState)
return true
}
// Shift + Left
if input.isCursorBackward || input.emacsKey == EmacsKey.backward, input.isShiftHold {
var index = state.markerIndex
@ -197,7 +219,7 @@ extension KeyHandler {
cursorIndex: state.cursorIndex,
markerIndex: index,
readings: state.readings,
nodeValuesArray: walkedAnchors.values
nodeValuesArray: compositor.walkedNodes.values
)
marking.tooltipForInputting = state.tooltipForInputting
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking)
@ -219,7 +241,7 @@ extension KeyHandler {
cursorIndex: state.cursorIndex,
markerIndex: index,
readings: state.readings,
nodeValuesArray: walkedAnchors.values
nodeValuesArray: compositor.walkedNodes.values
)
marking.tooltipForInputting = state.tooltipForInputting
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking)
@ -262,10 +284,9 @@ extension KeyHandler {
return true
}
compositor.insertReading(customPunctuation)
let textToCommit = commitOverflownCompositionAndWalk
compositor.insertKey(customPunctuation)
walk()
let inputting = buildInputtingState
inputting.textToCommit = textToCommit
stateCallback(inputting)
//
@ -276,7 +297,7 @@ extension KeyHandler {
isTypingVertical: isTypingVertical
)
if candidateState.candidates.count == 1 {
clear()
clear() // candidateState
if let candidateToCommit: (String, String) = candidateState.candidates.first, !candidateToCommit.1.isEmpty {
stateCallback(InputState.Committing(textToCommit: candidateToCommit.1))
stateCallback(InputState.Empty())
@ -302,7 +323,6 @@ extension KeyHandler {
) -> Bool {
guard let currentState = state as? InputState.Inputting else { return false }
clear()
stateCallback(InputState.Committing(textToCommit: currentState.composingBuffer))
stateCallback(InputState.Empty())
return true
@ -321,7 +341,7 @@ extension KeyHandler {
) -> Bool {
guard state is InputState.Inputting else { return false }
var composingBuffer = compositor.readings.joined(separator: "-")
var composingBuffer = compositor.keys.joined(separator: "-")
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
composingBuffer = Tekkon.restoreToneOneInZhuyinKey(target: composingBuffer) //
composingBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: composingBuffer) //
@ -331,8 +351,6 @@ extension KeyHandler {
composingBuffer = composingBuffer.replacingOccurrences(of: "-", with: " ")
}
clear()
stateCallback(InputState.Committing(textToCommit: composingBuffer))
stateCallback(InputState.Empty())
return true
@ -353,7 +371,7 @@ extension KeyHandler {
var composed = ""
for node in walkedAnchors.map(\.node) {
for node in compositor.walkedNodes {
var key = node.key
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
key = Tekkon.restoreToneOneInZhuyinKey(target: key) //
@ -364,13 +382,11 @@ extension KeyHandler {
key = Tekkon.cnvZhuyinChainToTextbookReading(target: key, newSeparator: " ")
}
let value = node.currentPair.value
let value = node.value
//
composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>"
}
clear()
stateCallback(InputState.Committing(textToCommit: composed))
stateCallback(InputState.Empty())
return true
@ -381,21 +397,29 @@ extension KeyHandler {
/// Backspace (macOS Delete)
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleBackSpace(
state: InputStateProtocol,
input: InputSignal,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
if input.isShiftHold {
stateCallback(InputState.EmptyIgnoringPreviousState())
stateCallback(InputState.Empty())
return true
}
if composer.hasToneMarker(withNothingElse: true) {
composer.clear()
} else if composer.isEmpty {
if compositor.cursor > 0 {
compositor.dropReading(direction: .rear)
compositor.dropKey(direction: .rear)
walk()
} else {
IME.prtDebugIntel("9D69908D")
@ -421,32 +445,38 @@ extension KeyHandler {
/// PC Delete (macOS Fn+BackSpace)
/// - Parameters:
/// - state:
/// - input:
/// - stateCallback:
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleDelete(
state: InputStateProtocol,
input: InputSignal,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
guard composer.isEmpty else {
IME.prtDebugIntel("9C69908D")
errorCallback()
stateCallback(state)
if input.isShiftHold {
stateCallback(InputState.EmptyIgnoringPreviousState())
stateCallback(InputState.Empty())
return true
}
guard compositor.cursor != compositor.length else {
if compositor.cursor == compositor.length, composer.isEmpty {
IME.prtDebugIntel("9B69938D")
errorCallback()
stateCallback(state)
return true
}
compositor.dropReading(direction: .front)
walk()
if composer.isEmpty {
compositor.dropKey(direction: .front)
walk()
} else {
composer.clear()
}
let inputting = buildInputtingState
// count > 0!isEmpty滿
switch inputting.composingBuffer.isEmpty {
@ -564,7 +594,6 @@ extension KeyHandler {
if mgrPrefs.escToCleanInputBuffer {
///
/// macOS Windows 使
clear()
stateCallback(InputState.EmptyIgnoringPreviousState())
stateCallback(InputState.Empty())
} else {
@ -614,7 +643,7 @@ extension KeyHandler {
composingBuffer: currentState.composingBuffer,
cursorIndex: currentState.cursorIndex,
markerIndex: nextPosition,
readings: compositor.readings
readings: compositor.keys
)
marking.tooltipForInputting = currentState.tooltip
stateCallback(marking)
@ -688,7 +717,7 @@ extension KeyHandler {
composingBuffer: currentState.composingBuffer,
cursorIndex: currentState.cursorIndex,
markerIndex: previousPosition,
readings: compositor.readings
readings: compositor.keys
)
marking.tooltipForInputting = currentState.tooltip
stateCallback(marking)
@ -744,7 +773,7 @@ extension KeyHandler {
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
if composer.isEmpty, compositor.isEmpty || walkedAnchors.isEmpty { return false }
if composer.isEmpty, compositor.isEmpty || compositor.walkedNodes.isEmpty { return false }
guard state is InputState.Inputting else {
guard state is InputState.Empty else {
IME.prtDebugIntel("6044F081")
@ -769,24 +798,27 @@ extension KeyHandler {
}
var length = 0
var currentAnchor = Megrez.NodeAnchor()
let cursorIndex = min(
actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositor.length
)
for anchor in walkedAnchors {
length += anchor.spanLength
if length >= cursorIndex {
currentAnchor = anchor
var currentNode: Megrez.Compositor.Node?
let cursorIndex = actualCandidateCursor
for node in compositor.walkedNodes {
length += node.spanLength
if length > cursorIndex {
currentNode = node
break
}
}
let currentNode = currentAnchor.node
let currentPaired: Megrez.KeyValuePaired = currentNode.currentPair
guard let currentNode = currentNode else {
IME.prtDebugIntel("F58DEA95")
errorCallback()
return true
}
let currentPaired = (currentNode.key, currentNode.value)
var currentIndex = 0
if currentNode.score < Megrez.Node.kSelectedCandidateScore {
/// 使
if !currentNode.isOverriden {
/// 使
/// 使
/// 2 使
///
@ -795,14 +827,14 @@ extension KeyHandler {
/// (Shift+)Tab ()
/// Shift(+CMD)+Space Alt+/ Alt+/
/// Tab
if candidates[0].0 == currentPaired.key, candidates[0].1 == currentPaired.value {
if candidates[0] == currentPaired {
///
///
currentIndex = reverseModifier ? candidates.count - 1 : 1
}
} else {
for candidate in candidates {
if candidate.0 == currentPaired.key, candidate.1 == currentPaired.value {
if candidate == currentPaired {
if reverseModifier {
if currentIndex == 0 {
currentIndex = candidates.count - 1

View File

@ -21,8 +21,8 @@ import InputMethodKit
/// IMKInputController
@objc(ctlInputMethod) // ObjC IMK ObjC
class ctlInputMethod: IMKInputController {
/// 使使
static var areWeDeleting = false
///
static var areWeNerfing = false
///
static var ctlCandidateCurrent: ctlCandidateProtocol = ctlCandidateUniversal.init(.horizontal)
@ -46,6 +46,9 @@ class ctlInputMethod: IMKInputController {
return isASCIIMode
}
/// `handle(event:)` Shift
private var rencentKeyHandledByKeyHandler = false
// MARK: -
///
@ -59,7 +62,6 @@ class ctlInputMethod: IMKInputController {
/// 調
handle(state: InputState.Committing(textToCommit: state.composingBufferConverted))
}
keyHandler.clear()
handle(state: InputState.Empty())
}
@ -93,7 +95,7 @@ class ctlInputMethod: IMKInputController {
//
if keyHandler.delegate == nil { keyHandler.delegate = self }
setValue(IME.currentInputMode.rawValue, forTag: 114_514, client: client())
keyHandler.clear()
keyHandler.clear() // handle State.Empty()
keyHandler.ensureParser()
if isASCIIMode {
@ -120,7 +122,6 @@ class ctlInputMethod: IMKInputController {
/// - Parameter sender: 使
override func deactivateServer(_ sender: Any!) {
_ = sender //
keyHandler.clear()
handle(state: InputState.Empty())
handle(state: InputState.Deactivated())
}
@ -146,7 +147,7 @@ class ctlInputMethod: IMKInputController {
if keyHandler.inputMode != newInputMode {
UserDefaults.standard.synchronize()
keyHandler.clear()
keyHandler.clear() // handle State.Empty()
keyHandler.inputMode = newInputMode
///
/// macOS
@ -188,13 +189,17 @@ class ctlInputMethod: IMKInputController {
// Shift
if ShiftKeyUpChecker.check(event) {
NotifierController.notify(
message: String(
format: "%@%@%@", NSLocalizedString("Alphanumerical Mode", comment: ""), "\n",
toggleASCIIMode()
? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "")
))
if !rencentKeyHandledByKeyHandler {
NotifierController.notify(
message: String(
format: "%@%@%@", NSLocalizedString("Alphanumerical Mode", comment: ""), "\n",
toggleASCIIMode()
? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "")
)
)
}
rencentKeyHandledByKeyHandler = false
return false
}
@ -204,8 +209,8 @@ class ctlInputMethod: IMKInputController {
/// NSInternalInconsistencyException
if event.type == .flagsChanged { return false }
// Enter
ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command])
//
ctlInputMethod.areWeNerfing = event.modifierFlags.contains([.shift, .command])
var textFrame = NSRect.zero
@ -240,6 +245,7 @@ class ctlInputMethod: IMKInputController {
} errorCallback: {
clsSFX.beep()
}
rencentKeyHandledByKeyHandler = result
return result
}

View File

@ -22,27 +22,22 @@ extension ctlInputMethod: KeyHandlerDelegate {
ctlCandidate(controller, didSelectCandidateAtIndex: index)
}
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol)
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol, addToFilter: Bool)
-> Bool
{
guard let state = state as? InputState.Marking else {
return false
}
if !state.validToWrite {
return false
}
guard let state = state as? InputState.Marking else { return false }
let refInputModeReversed: InputMode =
(keyHandler.inputMode == InputMode.imeModeCHT)
? InputMode.imeModeCHS : InputMode.imeModeCHT
if !mgrLangModel.writeUserPhrase(
state.userPhrase, inputMode: keyHandler.inputMode,
areWeDuplicating: state.chkIfUserPhraseExists,
areWeDeleting: ctlInputMethod.areWeDeleting
areWeDeleting: addToFilter
)
|| !mgrLangModel.writeUserPhrase(
state.userPhraseConverted, inputMode: refInputModeReversed,
areWeDuplicating: false,
areWeDeleting: ctlInputMethod.areWeDeleting
areWeDeleting: addToFilter
)
{
return false
@ -116,9 +111,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
let inputting = keyHandler.buildInputtingState
if mgrPrefs.useSCPCTypingMode {
keyHandler.clear()
let composingBuffer = inputting.composingBuffer
handle(state: InputState.Committing(textToCommit: composingBuffer))
handle(state: InputState.Committing(textToCommit: inputting.composingBuffer))
// selectedValue.1
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(

View File

@ -131,6 +131,8 @@ extension ctlInputMethod {
commit(text: previous.composingBuffer)
}
clearInlineDisplay()
//
keyHandler.clear()
}
private func handle(state: InputState.Empty, previous: InputStateProtocol) {
@ -147,6 +149,8 @@ extension ctlInputMethod {
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
clearInlineDisplay()
//
keyHandler.clear()
}
private func handle(
@ -167,6 +171,8 @@ extension ctlInputMethod {
commit(text: textToCommit)
}
clearInlineDisplay()
//
keyHandler.clear()
}
private func handle(state: InputState.Inputting, previous: InputStateProtocol) {

View File

@ -48,6 +48,7 @@ public enum UserDef: String, CaseIterable {
case kAlsoConfirmAssociatedCandidatesByEnter = "AlsoConfirmAssociatedCandidatesByEnter"
case kKeepReadingUponCompositionError = "KeepReadingUponCompositionError"
case kTogglingAlphanumericalModeWithLShift = "TogglingAlphanumericalModeWithLShift"
case kUpperCaseLetterKeyBehavior = "UpperCaseLetterKeyBehavior"
case kCandidateTextFontName = "CandidateTextFontName"
case kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
@ -70,15 +71,6 @@ private let kDefaultMinKeyLabelSize: CGFloat = 10
private let kMinCandidateListTextSize: CGFloat = 12
private let kMaxCandidateListTextSize: CGFloat = 196
// default, min and max composing buffer size (in codepoints)
// modern Macs can usually work up to 16 codepoints when the compositor still
// walks the grid with good performance slower Macs (like old PowerBooks)
// will start to sputter beyond 12 such is the algorithmatic complexity
// of the Viterbi algorithm used in the Megrez library (at O(N^2))
private let kDefaultComposingBufferSize = 20
private let kMinComposingBufferSize = 10
private let kMaxComposingBufferSize = 40
private let kDefaultKeys = "123456789"
// MARK: - UserDefaults extension.
@ -137,34 +129,6 @@ struct CandidateListTextSize {
}
}
@propertyWrapper
struct ComposingBufferSize {
let key: String
let defaultValue: Int = kDefaultComposingBufferSize
lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue)
var wrappedValue: Int {
mutating get {
let currentValue = container.wrappedValue
if currentValue < kMinComposingBufferSize {
return kMinComposingBufferSize
} else if currentValue > kMaxComposingBufferSize {
return kMaxComposingBufferSize
}
return currentValue
}
set {
var value = newValue
if value < kMinComposingBufferSize {
value = kMinComposingBufferSize
} else if value > kMaxComposingBufferSize {
value = kMaxComposingBufferSize
}
container.wrappedValue = value
}
}
}
// MARK: -
enum MandarinParser: Int {
@ -308,6 +272,9 @@ public enum mgrPrefs {
UserDefaults.standard.setDefault(
mgrPrefs.togglingAlphanumericalModeWithLShift, forKey: UserDef.kTogglingAlphanumericalModeWithLShift.rawValue
)
UserDefaults.standard.setDefault(
mgrPrefs.upperCaseLetterKeyBehavior, forKey: UserDef.kUpperCaseLetterKeyBehavior.rawValue
)
UserDefaults.standard.setDefault(mgrPrefs.usingHotKeySCPC, forKey: UserDef.kUsingHotKeySCPC.rawValue)
UserDefaults.standard.setDefault(mgrPrefs.usingHotKeyAssociates, forKey: UserDef.kUsingHotKeyAssociates.rawValue)
@ -388,8 +355,6 @@ public enum mgrPrefs {
@UserDefault(key: UserDef.kUseHorizontalCandidateList.rawValue, defaultValue: true)
static var useHorizontalCandidateList: Bool
static var composingBufferSize: Int { 30 }
@UserDefault(key: UserDef.kChooseCandidateUsingSpace.rawValue, defaultValue: true)
static var chooseCandidateUsingSpace: Bool
@ -408,9 +373,12 @@ public enum mgrPrefs {
@UserDefault(key: UserDef.kAlsoConfirmAssociatedCandidatesByEnter.rawValue, defaultValue: true)
static var alsoConfirmAssociatedCandidatesByEnter: Bool
@UserDefault(key: UserDef.kAlsoConfirmAssociatedCandidatesByEnter.rawValue, defaultValue: false)
@UserDefault(key: UserDef.kKeepReadingUponCompositionError.rawValue, defaultValue: false)
static var keepReadingUponCompositionError: Bool
@UserDefault(key: UserDef.kUpperCaseLetterKeyBehavior.rawValue, defaultValue: 0)
static var upperCaseLetterKeyBehavior: Int
// MARK: - Settings (Tier 2)
@UserDefault(key: UserDef.kTogglingAlphanumericalModeWithLShift.rawValue, defaultValue: true)

View File

@ -175,14 +175,8 @@ extension vChewing {
/// - Parameter key:
/// - Returns:
public func unigramsFor(key: String) -> [Megrez.Unigram] {
if key == " " {
///
let spaceUnigram = Megrez.Unigram(
keyValue: Megrez.KeyValuePaired(key: " ", value: " "),
score: 0
)
return [spaceUnigram]
}
///
if key == " " { return [.init(value: " ")] }
///
var rawAllUnigrams: [Megrez.Unigram] = []
@ -209,11 +203,11 @@ extension vChewing {
rawAllUnigrams.append(contentsOf: queryDateTimeUnigrams(with: key))
// Swift 使 NSOrderedSet
var filteredPairs: Set<Megrez.KeyValuePaired> = []
var filteredPairs: Set<String> = []
// KeyValuePair
for unigram in lmFiltered.unigramsFor(key: key) {
filteredPairs.insert(unigram.keyValue)
filteredPairs.insert(unigram.value)
}
return filterAndTransform(
@ -243,9 +237,6 @@ extension vChewing {
lmAssociates.hasValuesFor(pair: pair)
}
/// 滿 LangModelProtocol
public func bigramsFor(precedingKey _: String, key _: String) -> [Megrez.Bigram] { .init() }
// MARK: -
///
@ -255,20 +246,20 @@ extension vChewing {
/// - Returns:
func filterAndTransform(
unigrams: [Megrez.Unigram],
filter filteredPairs: Set<Megrez.KeyValuePaired>
filter filteredPairs: Set<String>
) -> [Megrez.Unigram] {
var results: [Megrez.Unigram] = []
var insertedPairs: Set<Megrez.KeyValuePaired> = []
var insertedPairs: Set<String> = []
for unigram in unigrams {
var pair: Megrez.KeyValuePaired = unigram.keyValue
if filteredPairs.contains(pair) { continue }
var theValue: String = unigram.value
if filteredPairs.contains(theValue) { continue }
if isPhraseReplacementEnabled {
let replacement = lmReplacements.valuesFor(key: pair.value)
if !replacement.isEmpty { pair.value = replacement }
let replacement = lmReplacements.valuesFor(key: theValue)
if !replacement.isEmpty { theValue = replacement }
}
if insertedPairs.contains(pair) { continue }
results.append(Megrez.Unigram(keyValue: pair, score: unigram.score))
insertedPairs.insert(pair)
if insertedPairs.contains(theValue) { continue }
results.append(Megrez.Unigram(value: theValue, score: unigram.score))
insertedPairs.insert(theValue)
}
return results
}

View File

@ -31,9 +31,9 @@ extension vChewing.LMInstantiator {
var date3 = ChineseConverter.convertArabicNumeralsToChinese(target: date2)
date3 = date3.replacingOccurrences(of: "年〇", with: "")
date3 = date3.replacingOccurrences(of: "月〇", with: "")
results.append(.init(keyValue: .init(key: key, value: date1), score: -94))
results.append(.init(keyValue: .init(key: key, value: date2), score: -95))
results.append(.init(keyValue: .init(key: key, value: date3), score: -96))
results.append(.init(value: date1, score: -94))
results.append(.init(value: date2, score: -95))
results.append(.init(value: date3, score: -96))
if let currentDateShortened = currentDateShortened, delta.year != 0 {
var dateAlt1: String = formatterDate1.string(from: currentDateShortened)
dateAlt1.regReplace(pattern: #"^0+"#)
@ -42,9 +42,9 @@ extension vChewing.LMInstantiator {
var dateAlt3 = ChineseConverter.convertArabicNumeralsToChinese(target: dateAlt2)
dateAlt3 = dateAlt3.replacingOccurrences(of: "年〇", with: "")
dateAlt3 = dateAlt3.replacingOccurrences(of: "月〇", with: "")
results.append(.init(keyValue: .init(key: key, value: dateAlt1), score: -97))
results.append(.init(keyValue: .init(key: key, value: dateAlt2), score: -98))
results.append(.init(keyValue: .init(key: key, value: dateAlt3), score: -99))
results.append(.init(value: dateAlt1, score: -97))
results.append(.init(value: dateAlt2, score: -98))
results.append(.init(value: dateAlt3, score: -99))
}
case "ㄕˊ-ㄐㄧㄢ":
let formatterTime1 = DateFormatter()
@ -56,9 +56,9 @@ extension vChewing.LMInstantiator {
let time1 = formatterTime1.string(from: currentDate)
let time2 = formatterTime2.string(from: currentDate)
let time3 = formatterTime3.string(from: currentDate)
results.append(.init(keyValue: .init(key: key, value: time1), score: -97))
results.append(.init(keyValue: .init(key: key, value: time2), score: -98))
results.append(.init(keyValue: .init(key: key, value: time3), score: -99))
results.append(.init(value: time1, score: -97))
results.append(.init(value: time2, score: -98))
results.append(.init(value: time3, score: -99))
case "ㄒㄧㄥ-ㄑㄧ", "ㄒㄧㄥ-ㄑㄧˊ":
let formatterWeek1 = DateFormatter()
let formatterWeek2 = DateFormatter()
@ -68,8 +68,8 @@ extension vChewing.LMInstantiator {
formatterWeek2.locale = theLocale
let week1 = formatterWeek1.string(from: currentDate)
let week2 = formatterWeek2.string(from: currentDate)
results.append(.init(keyValue: .init(key: key, value: week1), score: -98))
results.append(.init(keyValue: .init(key: key, value: week2), score: -99))
results.append(.init(value: week1, score: -98))
results.append(.init(value: week2, score: -99))
default: return .init()
}
return results

View File

@ -115,18 +115,6 @@ extension vChewing {
IME.prtDebugIntel(strDump)
}
/// 使 strData
///
///
/// - parameters:
/// - precedingKey:
/// - key:
public func bigramsFor(precedingKey: String, key: String) -> [Megrez.Bigram] {
// Swift
// [Megrez.Bigram]()
precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]()
}
/// strData
/// - parameters:
/// - key:
@ -136,7 +124,6 @@ extension vChewing {
for netaRange in arrRangeRecords {
let neta = strData[netaRange].split(separator: " ")
let theValue: String = shouldReverse ? String(neta[0]) : String(neta[1])
let kvPair = Megrez.KeyValuePaired(key: key, value: theValue)
var theScore = defaultScore
if neta.count >= 3, !shouldForceDefaultScore, !neta[2].contains("#") {
theScore = .init(String(neta[2])) ?? defaultScore
@ -144,7 +131,7 @@ extension vChewing {
if theScore > 0 {
theScore *= -1 //
}
grams.append(Megrez.Unigram(keyValue: kvPair, score: theScore))
grams.append(Megrez.Unigram(value: theValue, score: theScore))
}
}
return grams

View File

@ -108,18 +108,6 @@ extension vChewing {
IME.prtDebugIntel(strDump)
}
/// 使 UTF8
///
///
/// - parameters:
/// - precedingKey:
/// - key:
public func bigramsFor(precedingKey: String, key: String) -> [Megrez.Bigram] {
// Swift
// [Megrez.Bigram]()
precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]()
}
/// UTF8
/// - parameters:
/// - key:
@ -130,7 +118,6 @@ extension vChewing {
let strNetaSet = String(decoding: netaSet, as: UTF8.self)
let neta = Array(strNetaSet.split(separator: " ").reversed())
let theValue: String = .init(neta[0])
let kvPair = Megrez.KeyValuePaired(key: key, value: theValue)
var theScore = defaultScore
if neta.count >= 2, !shouldForceDefaultScore {
theScore = .init(String(neta[1])) ?? defaultScore
@ -138,7 +125,7 @@ extension vChewing {
if theScore > 0 {
theScore *= -1 //
}
grams.append(Megrez.Unigram(keyValue: kvPair, score: theScore))
grams.append(Megrez.Unigram(value: theValue, score: theScore))
}
}
return grams

View File

@ -26,176 +26,46 @@ extension vChewing {
mutDecayExponent = log(0.5) / decayConstant
}
public func observe(
walkedAnchors: [Megrez.NodeAnchor],
cursorIndex: Int,
candidate: String,
timestamp: Double,
saveCallback: @escaping () -> Void
public func performObservation(
walkedBefore: [Megrez.Compositor.Node], walkedAfter: [Megrez.Compositor.Node],
cursor: Int, timestamp: Double, saveCallback: @escaping () -> Void
) {
let key = convertKeyFrom(walkedAnchors: walkedAnchors, cursorIndex: cursorIndex)
//
guard !walkedAfter.isEmpty, !walkedBefore.isEmpty else { return }
guard walkedBefore.totalReadingsCount == walkedAfter.totalReadingsCount else { return }
//
var actualCursor = 0
guard let currentNode = walkedAfter.findNode(at: cursor, target: &actualCursor) else { return }
// 使
guard currentNode.spanLength <= 3 else { return }
//
guard actualCursor > 0 else { return } //
let currentNodeIndex = actualCursor
actualCursor -= 1
var prevNodeIndex = 0
guard let prevNode = walkedBefore.findNode(at: actualCursor, target: &prevNodeIndex) else { return }
let forceHighScoreOverride: Bool = currentNode.spanLength > prevNode.spanLength
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
let key: String = vChewing.LMUserOverride.formObservationKey(
walkedNodes: walkedAfter, headIndex: targetNodeIndex
)
guard !key.isEmpty else { return }
guard mutLRUMap[key] != nil else {
var observation: Observation = .init()
observation.update(candidate: candidate, timestamp: timestamp)
let koPair = KeyObservationPair(key: key, observation: observation)
// key key key
// Swift
mutLRUMap.removeValue(forKey: key)
mutLRUMap[key] = koPair
mutLRUList.insert(koPair, at: 0)
if mutLRUList.count > mutCapacity {
mutLRUMap.removeValue(forKey: mutLRUList[mutLRUList.endIndex].key)
mutLRUList.removeLast()
}
IME.prtDebugIntel("UOM: Observation finished with new observation: \(key)")
saveCallback()
return
}
//
if var theNeta = mutLRUMap[key] {
_ = suggest(
walkedAnchors: walkedAnchors, cursorIndex: cursorIndex, timestamp: timestamp,
decayCallback: {
theNeta.observation.update(candidate: candidate, timestamp: timestamp)
self.mutLRUList.insert(theNeta, at: 0)
self.mutLRUMap[key] = theNeta
IME.prtDebugIntel("UOM: Observation finished with existing observation: \(key)")
saveCallback()
}
)
}
doObservation(
key: key, candidate: currentNode.currentUnigram.value, timestamp: timestamp,
forceHighScoreOverride: forceHighScoreOverride, saveCallback: { saveCallback() }
)
}
public func suggest(
walkedAnchors: [Megrez.NodeAnchor],
cursorIndex: Int,
timestamp: Double,
decayCallback: @escaping () -> Void = {}
) -> [Megrez.Unigram] {
let key = convertKeyFrom(walkedAnchors: walkedAnchors, cursorIndex: cursorIndex)
guard !key.isEmpty else {
IME.prtDebugIntel("UOM: Blank key generated on suggestion, aborting suggestion.")
return .init()
}
let currentReadingKey = convertKeyFrom(walkedAnchors: walkedAnchors, cursorIndex: cursorIndex, readingOnly: true)
guard let koPair = mutLRUMap[key] else {
IME.prtDebugIntel("UOM: mutLRUMap[key] is nil, throwing blank suggestion for key: \(key).")
return .init()
}
let observation = koPair.observation
var arrResults = [Megrez.Unigram]()
var currentHighScore = 0.0
for overrideNeta in Array(observation.overrides) {
let override: Override = overrideNeta.value
let overrideScore: Double = getScore(
eventCount: override.count,
totalCount: observation.count,
eventTimestamp: override.timestamp,
timestamp: timestamp,
lambda: mutDecayExponent
)
if (0...currentHighScore).contains(overrideScore) { continue }
let overrideDetectionScore: Double = getScore(
eventCount: override.count,
totalCount: observation.count,
eventTimestamp: override.timestamp,
timestamp: timestamp,
lambda: mutDecayExponent * 2
)
if (0...currentHighScore).contains(overrideDetectionScore) { decayCallback() }
let newUnigram = Megrez.Unigram(
keyValue: .init(key: currentReadingKey, value: overrideNeta.key), score: overrideScore
)
arrResults.insert(newUnigram, at: 0)
currentHighScore = overrideScore
}
if arrResults.isEmpty {
IME.prtDebugIntel("UOM: No usable suggestions in the result for key: \(key).")
}
return arrResults
}
private func getScore(
eventCount: Int,
totalCount: Int,
eventTimestamp: Double,
timestamp: Double,
lambda: Double
) -> Double {
let decay = exp((timestamp - eventTimestamp) * lambda)
if decay < kDecayThreshold { return 0.0 }
let prob = Double(eventCount) / Double(totalCount)
return prob * decay
}
func convertKeyFrom(
walkedAnchors: [Megrez.NodeAnchor], cursorIndex: Int, readingOnly: Bool = false
) -> String {
let whiteList = "你他妳她祢衪它牠再在"
var arrNodes: [Megrez.NodeAnchor] = []
var intLength = 0
for theNodeAnchor in walkedAnchors {
arrNodes.append(theNodeAnchor)
intLength += theNodeAnchor.spanLength
if intLength >= cursorIndex {
break
}
}
if arrNodes.isEmpty { return "" }
arrNodes = Array(arrNodes.reversed())
let kvCurrent = arrNodes[0].node.currentPair
guard !kvCurrent.key.contains("_") else {
return ""
}
//
if kvCurrent.key.split(separator: "-").count != kvCurrent.value.count { return "" }
//
let strCurrent = kvCurrent.key
var kvPrevious = Megrez.KeyValuePaired()
var kvAnterior = Megrez.KeyValuePaired()
var readingStack = ""
var trigramKey: String { "(\(kvAnterior.toNGramKey),\(kvPrevious.toNGramKey),\(strCurrent))" }
var result: String {
// kvCurrent
if readingStack.contains("_")
|| (!kvPrevious.isValid && kvCurrent.value.count == 1 && !whiteList.contains(kvCurrent.value))
{
return ""
} else {
return (readingOnly ? strCurrent : trigramKey)
}
}
if arrNodes.count >= 2,
!kvPrevious.key.contains("_"),
kvPrevious.key.split(separator: "-").count == kvPrevious.value.count
{
kvPrevious = arrNodes[1].node.currentPair
readingStack = kvPrevious.key + readingStack
}
if arrNodes.count >= 3,
!kvAnterior.key.contains("_"),
kvAnterior.key.split(separator: "-").count == kvAnterior.value.count
{
kvAnterior = arrNodes[2].node.currentPair
readingStack = kvAnterior.key + readingStack
}
return result
public func fetchSuggestion(
currentWalk: [Megrez.Compositor.Node], cursor: Int, timestamp: Double
) -> Suggestion {
var headIndex = 0
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
let key = vChewing.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.key)
}
}
}
@ -210,6 +80,7 @@ extension vChewing.LMUserOverride {
struct Override: Hashable, Encodable, Decodable {
var count: Int = 0
var timestamp: Double = 0.0
var forceHighScoreOverride = false
static func == (lhs: Override, rhs: Override) -> Bool {
lhs.count == rhs.count && lhs.timestamp == rhs.timestamp
}
@ -244,11 +115,12 @@ extension vChewing.LMUserOverride {
hasher.combine(overrides)
}
mutating func update(candidate: String, timestamp: Double) {
mutating func update(candidate: String, timestamp: Double, forceHighScoreOverride: Bool = false) {
count += 1
if overrides.keys.contains(candidate) {
overrides[candidate]?.timestamp = timestamp
overrides[candidate]?.count += 1
overrides[candidate]?.forceHighScoreOverride = forceHighScoreOverride
} else {
overrides[candidate] = .init(count: 1, timestamp: timestamp)
}
@ -333,4 +205,203 @@ extension vChewing.LMUserOverride {
return
}
}
public struct Suggestion {
var candidates = [(String, Megrez.Unigram)]()
var forceHighScoreOverride = false
var isEmpty: Bool { candidates.isEmpty }
}
}
// MARK: - Array Extensions.
extension Array where Element == Megrez.Compositor.Node {
public var totalReadingsCount: Int {
var counter = 0
for node in self {
counter += node.keyArray.count
}
return counter
}
public func findNode(at cursor: Int, target outCursorPastNode: inout Int) -> Megrez.Compositor.Node? {
guard !isEmpty else { return nil }
let cursor = Swift.max(0, Swift.min(cursor, keys.count))
if cursor == 0, let theFirst = first {
outCursorPastNode = theFirst.spanLength
return theFirst
}
//
if cursor >= keys.count - 1, let theLast = last {
outCursorPastNode = keys.count
return theLast
}
var accumulated = 0
for neta in self {
accumulated += neta.spanLength
if accumulated > cursor {
outCursorPastNode = accumulated
return neta
}
}
//
return nil
}
}
// MARK: - Private Methods
extension vChewing.LMUserOverride {
private func doObservation(
key: String, candidate: String, timestamp: Double, forceHighScoreOverride: Bool,
saveCallback: @escaping () -> Void
) {
guard mutLRUMap[key] != nil else {
var observation: Observation = .init()
observation.update(candidate: candidate, timestamp: timestamp, forceHighScoreOverride: forceHighScoreOverride)
let koPair = KeyObservationPair(key: key, observation: observation)
// key key key
// Swift
mutLRUMap.removeValue(forKey: key)
mutLRUMap[key] = koPair
mutLRUList.insert(koPair, at: 0)
if mutLRUList.count > mutCapacity {
mutLRUMap.removeValue(forKey: mutLRUList[mutLRUList.endIndex].key)
mutLRUList.removeLast()
}
IME.prtDebugIntel("UOM: Observation finished with new observation: \(key)")
saveCallback()
return
}
// TODO:
if var theNeta = mutLRUMap[key] {
_ = getSuggestion(
key: key, timestamp: timestamp, headReading: "",
decayCallback: {
theNeta.observation.update(
candidate: candidate, timestamp: timestamp, forceHighScoreOverride: forceHighScoreOverride
)
self.mutLRUList.insert(theNeta, at: 0)
self.mutLRUMap[key] = theNeta
IME.prtDebugIntel("UOM: Observation finished with existing observation: \(key)")
saveCallback()
}
)
}
}
private func getSuggestion(
key: String, timestamp: Double, headReading: String, decayCallback: @escaping () -> Void = {}
) -> Suggestion {
guard !key.isEmpty, let kvPair = mutLRUMap[key] else { return .init() }
let observation: Observation = kvPair.observation
var candidates: [(String, Megrez.Unigram)] = .init()
var forceHighScoreOverride = false
var currentHighScore: Double = 0
for (i, theObservation) in observation.overrides {
let overrideScore = getScore(
eventCount: theObservation.count, totalCount: observation.count,
eventTimestamp: theObservation.timestamp, timestamp: timestamp, lambda: mutDecayExponent
)
if (0...currentHighScore).contains(overrideScore) { continue }
let overrideDetectionScore: Double = getScore(
eventCount: theObservation.count, totalCount: observation.count,
eventTimestamp: theObservation.timestamp, timestamp: timestamp, lambda: mutDecayExponent * 2
)
if (0...currentHighScore).contains(overrideDetectionScore) { decayCallback() }
candidates.append((headReading, .init(value: i, score: overrideScore)))
forceHighScoreOverride = theObservation.forceHighScoreOverride
currentHighScore = overrideScore
}
return .init(candidates: candidates, forceHighScoreOverride: forceHighScoreOverride)
}
private func getScore(
eventCount: Int,
totalCount: Int,
eventTimestamp: Double,
timestamp: Double,
lambda: Double
) -> Double {
let decay = exp((timestamp - eventTimestamp) * lambda)
if decay < kDecayThreshold { return 0.0 }
let prob = Double(eventCount) / Double(totalCount)
return prob * decay
}
private static func isPunctuation(_ node: Megrez.Compositor.Node) -> Bool {
for key in node.keyArray {
guard let firstChar = key.first else { continue }
return String(firstChar) == "_"
}
return false
}
private static func formObservationKey(
walkedNodes: [Megrez.Compositor.Node], headIndex cursorIndex: Int, readingOnly: Bool = false
) -> String {
let whiteList = "你他妳她祢衪它牠再在"
var arrNodes: [Megrez.Compositor.Node] = []
var intLength = 0
for theNodeAnchor in walkedNodes {
arrNodes.append(theNodeAnchor)
intLength += theNodeAnchor.spanLength
if intLength >= cursorIndex {
break
}
}
if arrNodes.isEmpty { return "" }
arrNodes = Array(arrNodes.reversed())
let kvCurrent = arrNodes[0].currentPair
guard !kvCurrent.key.contains("_") else {
return ""
}
//
if kvCurrent.key.split(separator: "-").count != kvCurrent.value.count { return "" }
//
let strCurrent = kvCurrent.key
var kvPrevious = Megrez.KeyValuePaired()
var kvAnterior = Megrez.KeyValuePaired()
var readingStack = ""
var trigramKey: String { "(\(kvAnterior.toNGramKey),\(kvPrevious.toNGramKey),\(strCurrent))" }
var result: String {
// kvCurrent
if readingStack.contains("_")
|| (!kvPrevious.isValid && kvCurrent.value.count == 1 && !whiteList.contains(kvCurrent.value))
{
return ""
} else {
return (readingOnly ? strCurrent : trigramKey)
}
}
if arrNodes.count >= 2,
!kvPrevious.key.contains("_"),
kvPrevious.key.split(separator: "-").count == kvPrevious.value.count
{
kvPrevious = arrNodes[1].currentPair
readingStack = kvPrevious.key + readingStack
}
if arrNodes.count >= 3,
!kvAnterior.key.contains("_"),
kvAnterior.key.split(separator: "-").count == kvAnterior.value.count
{
kvAnterior = arrNodes[2].currentPair
readingStack = kvAnterior.key + readingStack
}
return result
}
}

View File

@ -184,7 +184,7 @@ enum mgrLangModel {
(mode == InputMode.imeModeCHT)
? gLangModelCHT.unigramsFor(key: unigramKey) : gLangModelCHS.unigramsFor(key: unigramKey)
for unigram in unigrams {
if unigram.keyValue.value == userPhrase {
if unigram.value == userPhrase {
return true
}
}

View File

@ -1,11 +1,21 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
/// The namespace for this package.
public enum Megrez {}
public enum Megrez {
public typealias KeyValuePaired = Compositor.Candidate //
}
//
// Megrez Gramambular Lukhnos Liu
// Megrez Compositor Gramambular Swift
//
// Grid:
// Walk:
// Node:
// SpanLength:
// Span:

View File

@ -1,41 +1,91 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
public class Compositor: Grid {
///
///
///
///
///
/// - Remark: Markov HMM
///
///
///
public class Compositor {
///
public enum TypingDirection { case front, rear }
///
private let kDroppedPathScore: Double = -999
///
public enum ResizeBehavior { case expand, shrink }
///
public static var maxSpanLength: Int = 10 { didSet { maxSpanLength = max(6, maxSpanLength) } }
/// -
public static let kDefaultSeparator: String = "-"
///
public var cursor: Int = 0 { didSet { cursor = max(0, min(cursor, readings.count)) } }
///
private(set) var readings: [String] = []
/// 使
private var langModel: LangModelProtocol
public var cursor: Int = 0 { didSet { cursor = max(0, min(cursor, length)) } }
/// -
public var separator = kDefaultSeparator
///
public var width: Int { keys.count }
///
public var walkedNodes: [Node] = []
///
public var length: Int { keys.count }
///
public var isEmpty: Bool { spans.isEmpty && keys.isEmpty }
///
private(set) var keys = [String]()
///
private(set) var spans = [Span]()
/// 使 LangModelRanked
private(set) var langModel: LangModelRanked
/// 0
private(set) var cursorRegionMap: [Int: Int] = .init()
///
private(set) var walkedAnchors: [NodeAnchor] = []
///
/// - Parameter nodes:
public func updateWalkedAnchors(with nodes: [Node]) {
walkedAnchors = nodes.map { Megrez.NodeAnchor(node: $0) }
///
/// - Parameter langModel:
public init(with langModel: LangModelProtocol, separator: String = "-") {
self.langModel = .init(withLM: langModel)
self.separator = separator
}
///
public var joinSeparator: String = "-"
public func clear() {
cursor = 0
keys.removeAll()
spans.removeAll()
walkedNodes.removeAll()
cursorRegionMap.removeAll()
}
///
public var length: Int { readings.count }
///
/// - Parameter key:
/// - Returns:
@discardableResult public func insertKey(_ key: String) -> Bool {
guard !key.isEmpty, key != separator, langModel.hasUnigramsFor(key: key) else { return false }
keys.insert(key, at: cursor)
resizeGrid(at: cursor, do: .expand)
update()
cursor += 1 // update()
return true
}
///
///
/// RearFront
///
/// - Parameter direction:
/// - Returns:
@discardableResult public func dropKey(direction: TypingDirection) -> Bool {
let isBackSpace: Bool = direction == .rear ? true : false
guard cursor != (isBackSpace ? 0 : keys.count) else { return false }
keys.remove(at: cursor - (isBackSpace ? 1 : 0))
cursor -= isBackSpace ? 1 : 0 //
resizeGrid(at: cursor, do: .shrink)
update()
return true
}
///
/// - Parameter direction:
@ -50,21 +100,21 @@ extension Megrez {
guard let currentRegion = cursorRegionMap[cursor] else { return false }
let aRegionForward = max(currentRegion - 1, 0)
let currentRegionBorderRear: Int = walkedAnchors[0..<currentRegion].map(\.spanLength).reduce(0, +)
let currentRegionBorderRear: Int = walkedNodes[0..<currentRegion].map(\.spanLength).reduce(0, +)
switch cursor {
case currentRegionBorderRear:
switch direction {
case .front:
cursor =
(currentRegion > walkedAnchors.count)
? readings.count : walkedAnchors[0...currentRegion].map(\.spanLength).reduce(0, +)
(currentRegion > walkedNodes.count)
? keys.count : walkedNodes[0...currentRegion].map(\.spanLength).reduce(0, +)
case .rear:
cursor = walkedAnchors[0..<aRegionForward].map(\.spanLength).reduce(0, +)
cursor = walkedNodes[0..<aRegionForward].map(\.spanLength).reduce(0, +)
}
default:
switch direction {
case .front:
cursor = currentRegionBorderRear + walkedAnchors[currentRegion].spanLength
cursor = currentRegionBorderRear + walkedNodes[currentRegion].spanLength
case .rear:
cursor = currentRegionBorderRear
}
@ -72,240 +122,150 @@ extension Megrez {
return true
}
///
/// - Parameters:
/// - lm: Megrez.LangModel
/// - length: 10
/// - separator:
public init(lm: LangModelProtocol, length: Int = 10, separator: String = "-") {
langModel = lm
super.init(spanLengthLimit: abs(length)) //
joinSeparator = separator
}
///
override public func clear() {
super.clear()
cursor = 0
readings.removeAll()
walkedAnchors.removeAll()
}
///
/// - Parameters:
/// - reading:
@discardableResult public func insertReading(_ reading: String) -> Bool {
guard !reading.isEmpty, langModel.hasUnigramsFor(key: reading) else { return false }
readings.insert(reading, at: cursor)
resizeGridByOneAt(location: cursor, to: .expand)
build()
cursor += 1
return true
}
///
///
/// RearFront
/// - Parameter direction:
/// - Returns:
@discardableResult public func dropReading(direction: TypingDirection) -> Bool {
let isBackSpace = direction == .rear
if cursor == (isBackSpace ? 0 : readings.count) {
return false
}
readings.remove(at: cursor - (isBackSpace ? 1 : 0))
cursor -= (isBackSpace ? 1 : 0)
resizeGridByOneAt(location: cursor, to: .shrink)
build()
return true
}
/// X
///
///
///
@discardableResult public func removeHeadReadings(count: Int) -> Bool {
let count = abs(count) //
if count > length { return false }
for _ in 0..<count {
cursor = max(cursor - 1, 0)
if !readings.isEmpty {
readings.removeFirst()
resizeGridByOneAt(location: 0, to: .shrink)
}
build()
}
return true
}
///
/// - Returns:
@discardableResult public func walk() -> [NodeAnchor] {
let newLocation = width
//
walkedAnchors = Array(
reverseWalk(at: newLocation).reversed()
).lazy.filter { !$0.isEmpty }
updateCursorJumpingTables(walkedAnchors)
return walkedAnchors
}
// MARK: - Private functions
///
/// - Parameters:
/// - location:
/// - mass: 0
/// - joinedPhrase: 使
/// - longPhrases: 使
/// - Returns:
private func reverseWalk(
at location: Int,
mass: Double = 0.0,
joinedPhrase: String = "",
longPhrases: [String] = .init()
) -> [NodeAnchor] {
let location = abs(location) //
if location == 0 || location > width {
return .init()
}
var paths = [[NodeAnchor]]()
let nodes = nodesEndingAt(location: location).stableSorted {
$0.node.score > $1.node.score
}
guard !nodes.isEmpty else { return .init() } //
if nodes[0].node.score >= Node.kSelectedCandidateScore {
// 使
var theAnchor = nodes[0]
theAnchor.mass = mass + nodes[0].node.score
var path: [NodeAnchor] = reverseWalk(
at: location - theAnchor.spanLength, mass: theAnchor.mass
)
path.insert(theAnchor, at: 0)
paths.append(path)
} else if !longPhrases.isEmpty {
var path = [NodeAnchor]()
for theAnchor in nodes {
var theAnchor = theAnchor
let joinedValue = theAnchor.node.currentPair.value + joinedPhrase
//
// /////////使
//
if longPhrases.contains(joinedValue) {
theAnchor.mass = kDroppedPathScore
path.insert(theAnchor, at: 0)
paths.append(path)
continue
/// GraphViz
public var dumpDOT: String {
var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
for (p, span) in spans.enumerated() {
for ni in 0...(span.maxLength) {
guard let np = span.nodeOf(length: ni) else { continue }
if p == 0 {
strOutput += "BOS -> \(np.value);\n"
}
theAnchor.mass = mass + theAnchor.node.score
path = reverseWalk(
at: location - theAnchor.spanLength,
mass: theAnchor.mass,
joinedPhrase: (joinedValue.count >= longPhrases[0].count) ? "" : joinedValue,
longPhrases: .init()
)
path.insert(theAnchor, at: 0)
paths.append(path)
}
} else {
//
var longPhrases = [String]()
for theAnchor in nodes.lazy.filter({ $0.spanLength > 1 }) {
longPhrases.append(theAnchor.node.currentPair.value)
}
longPhrases = longPhrases.stableSorted {
$0.count > $1.count
}
for theAnchor in nodes {
var theAnchor = theAnchor
theAnchor.mass = mass + theAnchor.node.score
var path = [NodeAnchor]()
path = reverseWalk(
at: location - theAnchor.spanLength, mass: theAnchor.mass,
joinedPhrase: (theAnchor.spanLength > 1) ? "" : theAnchor.node.currentPair.value,
longPhrases: .init()
)
path.insert(theAnchor, at: 0)
paths.append(path)
strOutput += "\(np.value);\n"
if (p + ni) < spans.count {
let destinationSpan = spans[p + ni]
for q in 0...(destinationSpan.maxLength) {
guard let dn = destinationSpan.nodeOf(length: q) else { continue }
strOutput += np.value + " -> " + dn.value + ";\n"
}
}
guard (p + ni) == spans.count else { continue }
strOutput += np.value + " -> EOS;\n"
}
}
guard !paths.isEmpty else {
return .init()
}
var result: [NodeAnchor] = paths[0]
for neta in paths.lazy.filter({
$0.last!.mass > result.last!.mass
}) {
result = neta
}
return result // walk()
}
private func build() {
let itrBegin: Int =
(cursor < maxBuildSpanLength) ? 0 : cursor - maxBuildSpanLength
let itrEnd: Int = min(cursor + maxBuildSpanLength, readings.count)
for p in itrBegin..<itrEnd {
for q in 1..<maxBuildSpanLength {
if p + q > itrEnd { break }
let arrSlice = readings[p..<(p + q)]
let combinedReading: String = join(slice: arrSlice, separator: joinSeparator)
if hasMatchedNode(location: p, spanLength: q, key: combinedReading) { continue }
let unigrams: [Unigram] = langModel.unigramsFor(key: combinedReading)
if unigrams.isEmpty { continue }
let n: Node = .init(key: combinedReading, spanLength: q, unigrams: unigrams)
insertNode(node: n, location: p, spanLength: q)
}
}
}
private func join(slice arrSlice: ArraySlice<String>, separator: String) -> String {
arrSlice.joined(separator: separator)
}
internal func updateCursorJumpingTables(_ anchors: [NodeAnchor]) {
var cursorRegionMapDict = [Int: Int]()
cursorRegionMapDict[-1] = 0 //
var counter = 0
for (i, anchor) in anchors.enumerated() {
for _ in 0..<anchor.spanLength {
cursorRegionMapDict[counter] = i
counter += 1
}
}
cursorRegionMapDict[counter] = anchors.count
cursorRegionMap = cursorRegionMapDict
strOutput += "EOS;\n}\n"
return strOutput
}
}
}
// MARK: - Stable Sort Extension
// MARK: - Internal Methods
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Megrez.Compositor {
// MARK: Internal methods for maintaining the grid.
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameters:
/// - location:
/// - action:
func resizeGrid(at location: Int, do action: ResizeBehavior) {
let location = max(min(location, spans.count), 0) //
switch action {
case .expand:
spans.insert(Span(), at: location)
if [0, spans.count].contains(location) { return }
case .shrink:
if spans.count == location { return }
spans.remove(at: location)
}
dropWreckedNodes(at: location)
}
/// resizeGrid()
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate func stableSorted(
by areInIncreasingOrder: (Element, Element) throws -> Bool
)
rethrows -> [Element]
{
try enumerated()
.sorted { a, b -> Bool in
try areInIncreasingOrder(a.element, b.element)
|| (a.offset < b.offset && !areInIncreasingOrder(b.element, a.element))
///
/// ```
/// Span Index 0 1 2 3
/// (---)
/// (-------)
/// (-----------)
/// ```
/// 2 (SpanIndex = 2) :
/// ```
/// Span Index 0 1 2 3 4
/// (---)
/// (XXX? ?XXX) <-
/// (XXXXXXX? ?XXX) <-
/// ```
///
/// ```
/// Span Index 0 1 2 3
/// (---)
/// (-------)
/// (-----------)
/// ```
/// 2 :
/// ```
/// Span Index 0 1 2 3 4
/// (---)
/// (XXX? <-
/// (XXXXXXX? <-
/// ```
/// - Parameter location:
func dropWreckedNodes(at location: Int) {
let location = max(min(location, spans.count), 0) //
guard !spans.isEmpty else { return }
let affectedLength = Megrez.Compositor.maxSpanLength - 1
let begin = max(0, location - affectedLength)
guard location >= begin else { return }
for i in begin..<location {
spans[i].dropNodesOfOrBeyond(length: location - i + 1)
}
}
@discardableResult func insertNode(_ node: Node, at location: Int) -> Bool {
let location = max(min(location, spans.count - 1), 0) //
spans[location].append(node: node)
return true
}
func getJointKey(range: Range<Int>) -> String {
// contains macOS 13 Ventura
guard range.upperBound <= keys.count, range.lowerBound >= 0 else { return "" }
return keys[range].joined(separator: separator)
}
func getJointKeyArray(range: Range<Int>) -> [String] {
// contains macOS 13 Ventura
guard range.upperBound <= keys.count, range.lowerBound >= 0 else { return [] }
return keys[range].map { String($0) }
}
func hasNode(at location: Int, length: Int, key: String) -> Bool {
let location = max(min(location, spans.count), 0) //
guard let node = spans[location].nodeOf(length: length) else { return false }
return key == node.key
}
func update() {
let maxSpanLength = Megrez.Compositor.maxSpanLength
let range = max(0, cursor - maxSpanLength)..<min(cursor + maxSpanLength, keys.count)
for position in range {
for theLength in 1...min(maxSpanLength, range.upperBound - position) {
let jointKeyArray = getJointKeyArray(range: position..<(position + theLength))
let jointKey = getJointKey(range: position..<(position + theLength))
if hasNode(at: position, length: theLength, key: jointKey) { continue }
let unigrams = langModel.unigramsFor(key: jointKey)
guard !unigrams.isEmpty else { continue }
insertNode(
.init(keyArray: jointKeyArray, spanLength: theLength, unigrams: unigrams, keySeparator: separator),
at: position
)
}
.map(\.element)
}
}
func updateCursorJumpingTables(_ walkedNodes: [Node]) {
var cursorRegionMapDict = [Int: Int]()
cursorRegionMapDict[-1] = 0 //
var counter = 0
for (i, anchor) in walkedNodes.enumerated() {
for _ in 0..<anchor.spanLength {
cursorRegionMapDict[counter] = i
counter += 1
}
}
cursorRegionMapDict[counter] = walkedNodes.count
cursorRegionMap = cursorRegionMapDict
}
}

View File

@ -1,250 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
public class Grid {
///
public enum ResizeBehavior { case expand, shrink }
///
private(set) var spans: [Megrez.SpanUnit]
///
private(set) var maxBuildSpanLength = 10
///
public var width: Int { spans.count }
///
public var isEmpty: Bool { spans.isEmpty }
///
public init(spanLengthLimit: Int = 10) {
maxBuildSpanLength = spanLengthLimit
spans = [Megrez.SpanUnit]()
}
///
public func clear() {
spans.removeAll()
}
///
/// - Parameters:
/// - node:
/// - location:
/// - spanLength:
public func insertNode(node: Node, location: Int, spanLength: Int) {
let location = abs(location) //
let spanLength = abs(spanLength) //
if location >= spans.count {
let diff = location - spans.count + 1
for _ in 0..<diff {
spans.append(SpanUnit())
}
}
spans[location].insert(node: node, length: spanLength)
}
///
/// - Parameters:
/// - location:
/// - spanLength:
/// - key:
public func hasMatchedNode(location: Int, spanLength: Int, key: String) -> Bool {
let location = abs(location) //
let spanLength = abs(spanLength) //
if location > spans.count {
return false
}
let n = spans[location].nodeOf(length: spanLength)
return n != nil && key == n?.key
}
///
/// - Parameters:
/// - location:
public func resizeGridByOneAt(location: Int, to behavior: ResizeBehavior) {
let location = max(0, min(width, location)) //
switch behavior {
case .expand:
spans.insert(SpanUnit(), at: location)
if [spans.count, 0].contains(location) { return }
case .shrink:
if location >= spans.count { return }
spans.remove(at: location)
}
for i in 0..<location {
//
spans[i].dropNodesBeyond(length: location - i)
}
}
///
/// - Parameters:
/// - location:
public func nodesBeginningAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if location >= spans.count { return results }
// spans location 0
let span = spans[location]
for i in 1...maxBuildSpanLength {
if let np = span.nodeOf(length: i) {
results.append(.init(node: np))
}
}
return results //
}
///
/// - Parameters:
/// - location:
public func nodesEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
let span = spans[i]
if i + span.maxLength < location { continue }
if let np = span.nodeOf(length: location - i) {
results.append(.init(node: np))
}
}
return results //
}
///
/// - Parameters:
/// - location:
public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
let span = spans[i]
if i + span.maxLength < location { continue }
for j in 1...span.maxLength {
if i + j < location { continue }
if let np = span.nodeOf(length: j) {
results.append(.init(node: np))
}
}
}
return results //
}
///
///
///
/// - Parameters:
/// - location:
public func nodesOverlappedAt(location: Int) -> [NodeAnchor] {
Array(Set(nodesBeginningAt(location: location) + nodesCrossingOrEndingAt(location: location)))
}
/// 使
///
/// fixNodeWithCandidate()
/// - Parameters:
/// - location:
/// - value:
@discardableResult public func fixNodeWithCandidateLiteral(_ value: String, at location: Int) -> NodeAnchor {
let location = abs(location) //
var node = NodeAnchor()
for theAnchor in nodesCrossingOrEndingAt(location: location) {
let candidates = theAnchor.node.candidates
//
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate.value == value {
theAnchor.node.selectCandidateAt(index: i)
node = theAnchor
break
}
}
}
return node
}
/// 使
///
///
/// - Parameters:
/// - location:
/// - value:
@discardableResult public func fixNodeWithCandidate(_ pair: KeyValuePaired, at location: Int) -> NodeAnchor {
let location = abs(location) //
var node = NodeAnchor()
for theAnchor in nodesCrossingOrEndingAt(location: location) {
let candidates = theAnchor.node.candidates
//
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate == pair {
theAnchor.node.selectCandidateAt(index: i)
node = theAnchor
break
}
}
}
return node
}
///
/// - Parameters:
/// - location:
/// - value:
/// - overridingScore:
public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) {
let location = abs(location) //
for theAnchor in nodesOverlappedAt(location: location) {
let candidates = theAnchor.node.candidates
//
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate.value == value {
theAnchor.node.selectFloatingCandidateAt(index: i, score: overridingScore)
break
}
}
}
}
}
}
// MARK: - DumpDOT-related functions.
extension Megrez.Grid {
/// GraphViz
public var dumpDOT: String {
var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
for (p, span) in spans.enumerated() {
for ni in 0...(span.maxLength) {
guard let np = span.nodeOf(length: ni) else { continue }
if p == 0 {
strOutput += "BOS -> \(np.currentPair.value);\n"
}
strOutput += "\(np.currentPair.value);\n"
if (p + ni) < spans.count {
let destinationSpan = spans[p + ni]
for q in 0...(destinationSpan.maxLength) {
guard let dn = destinationSpan.nodeOf(length: q) else { continue }
strOutput += np.currentPair.value + " -> " + dn.currentPair.value + ";\n"
}
}
guard (p + ni) == spans.count else { continue }
strOutput += np.currentPair.value + " -> EOS;\n"
}
}
strOutput += "EOS;\n}\n"
return strOutput
}
}

View File

@ -0,0 +1,107 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
/// 使 Cormen 2001
///
///
/// `G = (V, E)` `O(|V|+|E|)` `G`
/// 使
/// - Returns:
@discardableResult public func walk() -> ([Node], Bool) {
var result = [Node]()
defer {
walkedNodes = result
updateCursorJumpingTables(walkedNodes)
}
guard !spans.isEmpty else { return (result, true) }
var vertexSpans = [VertexSpan]()
for _ in spans {
vertexSpans.append(.init())
}
for (i, span) in spans.enumerated() {
for j in 1...span.maxLength {
if let p = span.nodeOf(length: j) {
vertexSpans[i].append(.init(node: p))
}
}
}
let terminal = Vertex(node: .init(keyArray: ["_TERMINAL_"], keySeparator: separator))
for (i, vertexSpan) in vertexSpans.enumerated() {
for vertex in vertexSpan {
let nextVertexPosition = i + vertex.node.spanLength
if nextVertexPosition == vertexSpans.count {
vertex.edges.append(terminal)
continue
}
for nextVertex in vertexSpans[nextVertexPosition] {
vertex.edges.append(nextVertex)
}
}
}
let root = Vertex(node: .init(keyArray: ["_ROOT_"], keySeparator: separator))
root.distance = 0
root.edges.append(contentsOf: vertexSpans[0])
var ordered: [Vertex] = topologicalSort(root: root)
for (j, neta) in ordered.reversed().enumerated() {
for (k, _) in neta.edges.enumerated() {
relax(u: neta, v: &neta.edges[k])
}
ordered[j] = neta
}
var walked = [Node]()
var totalKeyLength = 0
var it = terminal
while let itPrev = it.prev {
walked.append(itPrev.node)
it = itPrev
totalKeyLength += it.node.spanLength
}
guard totalKeyLength == keys.count else {
print("!!! ERROR A")
return (result, false)
}
guard walked.count >= 2 else {
print("!!! ERROR B")
return (result, false)
}
walked = walked.reversed()
walked.removeFirst()
result = walked
return (result, true)
}
}
// MARK: - Stable Sort Extension
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate func stableSorted(
by areInIncreasingOrder: (Element, Element) throws -> Bool
)
rethrows -> [Element]
{
try enumerated()
.sorted { a, b -> Bool in
try areInIncreasingOrder(a.element, b.element)
|| (a.offset < b.offset && !areInIncreasingOrder(b.element, a.element))
}
.map(\.element)
}
}

View File

@ -0,0 +1,181 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
import Foundation
extension Megrez.Compositor {
public struct Candidate: Equatable, Hashable, Comparable, CustomStringConvertible {
///
public var key: String
///
public var value: String
///
public var description: String { "(" + key + "," + value + ")" }
/// false
public var isValid: Bool { !key.isEmpty && !value.isEmpty }
/// ()
public var toNGramKey: String { !isValid ? "()" : "(" + key + "," + value + ")" }
///
/// - Parameters:
/// - key:
/// - value:
public init(key: String = "", value: String = "") {
self.key = key
self.value = value
}
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(value)
}
public static func == (lhs: Candidate, rhs: Candidate) -> Bool {
lhs.key == rhs.key && lhs.value == rhs.value
}
public static func < (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count < rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value < rhs.value)
}
public static func > (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count > rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value > rhs.value)
}
public static func <= (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count <= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value <= rhs.value)
}
public static func >= (lhs: Candidate, rhs: Candidate) -> Bool {
(lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value)
}
}
public enum CandidateFetchFilter { case all, beginAt, endAt }
///
///
/// location - 1
/// - Parameter location:
/// - Returns:
public func fetchCandidates(at location: Int, filter: CandidateFetchFilter = .all) -> [Candidate] {
var result = [Candidate]()
guard !keys.isEmpty else { return result }
let location = max(min(location, keys.count - 1), 0) //
let anchors: [NodeAnchor] = fetchOverlappingNodes(at: location).stableSorted {
//
$0.spanLength > $1.spanLength
}
let keyAtCursor = keys[location]
for theNode in anchors.map(\.node) {
if theNode.key.isEmpty { continue }
for gram in theNode.unigrams {
switch filter {
case .all:
//
if !theNode.keyArray.contains(keyAtCursor) { continue }
case .beginAt:
if theNode.keyArray[0] != keyAtCursor { continue }
case .endAt:
if theNode.keyArray.reversed()[0] != keyAtCursor { continue }
}
result.append(.init(key: theNode.key, value: gram.value))
}
}
return result
}
/// 使
///
///
/// - Parameters:
/// - candidate:
/// - location:
/// - overrideType:
/// - Returns:
@discardableResult public func overrideCandidate(
_ candidate: Candidate, at location: Int, overrideType: Node.OverrideType = .withHighScore
)
-> Bool
{
overrideCandidateAgainst(key: candidate.key, at: location, value: candidate.value, type: overrideType)
}
/// 使
///
///
/// - Parameters:
/// - candidate:
/// - location:
/// - overrideType:
/// - Returns:
@discardableResult public func overrideCandidateLiteral(
_ candidate: String,
at location: Int, overrideType: Node.OverrideType = .withHighScore
) -> Bool {
overrideCandidateAgainst(key: nil, at: location, value: candidate, type: overrideType)
}
// MARK: Internal implementations.
/// 使
/// - Parameters:
/// - key:
/// - location:
/// - value:
/// - type:
/// - Returns:
internal func overrideCandidateAgainst(key: String?, at location: Int, value: String, type: Node.OverrideType)
-> Bool
{
let location = max(min(location, keys.count), 0) //
var arrOverlappedNodes: [NodeAnchor] = fetchOverlappingNodes(at: min(keys.count - 1, location))
var overridden: NodeAnchor?
for anchor in arrOverlappedNodes {
if let key = key, anchor.node.key != key { continue }
if anchor.node.selectOverrideUnigram(value: value, type: type) {
overridden = anchor
break
}
}
guard let overridden = overridden else { return false } //
for i in overridden.spanIndex..<min(spans.count, overridden.spanIndex + overridden.node.spanLength) {
/// A BC
/// A BC 使 A
/// DEF BC A
arrOverlappedNodes = fetchOverlappingNodes(at: i)
for anchor in arrOverlappedNodes {
if anchor.node == overridden.node { continue }
anchor.node.reset()
}
}
return true
}
}
// MARK: - Stable Sort Extension
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate func stableSorted(
by areInIncreasingOrder: (Element, Element) throws -> Bool
)
rethrows -> [Element]
{
try enumerated()
.sorted { a, b -> Bool in
try areInIncreasingOrder(a.element, b.element)
|| (a.offset < b.offset && !areInIncreasingOrder(b.element, a.element))
}
.map(\.element)
}
}

View File

@ -1,78 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct NodeAnchor: Hashable {
///
public var isEmpty: Bool { node.key.isEmpty }
///
public var node: Node = .init()
///
public var spanLength: Int { node.spanLength }
///
public var scoreForSort: Double { node.score }
///
public var mass: Double = 0.0
///
public var unigrams: [Unigram] { node.unigrams }
///
public var bigrams: [Bigram] { node.bigrams }
///
public var key: String { node.key }
///
public init(node: Node = .init(), mass: Double? = nil) {
self.node = node
self.mass = mass ?? self.node.score
}
///
public func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(mass)
}
///
public var description: String {
var stream = ""
stream += "{@(" + String(spanLength) + "),"
if node.key.isEmpty {
stream += node.description
} else {
stream += "null"
}
stream += "}"
return stream
}
}
}
// MARK: - Array Extensions.
extension Array where Element == Megrez.NodeAnchor {
///
public var description: String {
var arrOutputContent = [""]
for anchor in self {
arrOutputContent.append(anchor.description)
}
return arrOutputContent.joined(separator: "<-")
}
///
public var values: [String] {
map(\.node.currentPair.value)
}
///
public var keys: [String] {
map(\.node.currentPair.key)
}
}

View File

@ -1,63 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct SpanUnit {
///
private var lengthNodeMap: [Int: Megrez.Node] = [:]
///
private(set) var maxLength: Int = 0
///
mutating func clear() {
lengthNodeMap.removeAll()
maxLength = 0
}
///
/// - Parameters:
/// - node:
/// - length:
mutating func insert(node: Node, length: Int) {
let length = abs(length) //
lengthNodeMap[length] = node
maxLength = max(maxLength, length)
}
///
/// - Parameters:
/// - length:
mutating func dropNodesBeyond(length: Int) {
let length = abs(length) //
if length > maxLength { return }
var lenMax = 0
var removalList: [Int: Megrez.Node] = [:]
for key in lengthNodeMap.keys {
if key > length {
removalList[key] = lengthNodeMap[key]
} else {
lenMax = max(lenMax, key)
}
}
for key in removalList.keys {
lengthNodeMap.removeValue(forKey: key)
}
maxLength = lenMax
}
///
/// - Parameters:
/// - length:
public func nodeOf(length: Int) -> Node? {
// Abs()
lengthNodeMap.keys.contains(abs(length)) ? lengthNodeMap[abs(length)] : nil
}
}
}

View File

@ -1,172 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
public class Node: Equatable, Hashable {
public static func == (lhs: Megrez.Node, rhs: Megrez.Node) -> Bool {
lhs.key == rhs.key && lhs.score == rhs.score && lhs.unigrams == rhs.unigrams && lhs.bigrams == rhs.bigrams
&& lhs.candidates == rhs.candidates && lhs.valueUnigramIndexMap == rhs.valueUnigramIndexMap
&& lhs.precedingBigramMap == rhs.precedingBigramMap && lhs.isCandidateFixed == rhs.isCandidateFixed
&& lhs.selectedUnigramIndex == rhs.selectedUnigramIndex && lhs.spanLength == rhs.spanLength
}
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(score)
hasher.combine(unigrams)
hasher.combine(bigrams)
hasher.combine(spanLength)
hasher.combine(candidates)
hasher.combine(valueUnigramIndexMap)
hasher.combine(precedingBigramMap)
hasher.combine(isCandidateFixed)
hasher.combine(selectedUnigramIndex)
}
///
private(set) var key: String = ""
///
private(set) var score: Double = 0
///
private(set) var unigrams: [Unigram]
///
private(set) var bigrams: [Bigram]
///
public var spanLength: Int = 0
///
private(set) var candidates: [KeyValuePaired] = []
/// 調
private var valueUnigramIndexMap: [String: Int] = [:]
///
private var precedingBigramMap: [KeyValuePaired: [Megrez.Bigram]] = [:]
///
private(set) var isCandidateFixed: Bool = false
///
private var selectedUnigramIndex: Int = 0
///
public static let kSelectedCandidateScore: Double = 99
///
public var description: String {
"(node,key:\(key),fixed:\(isCandidateFixed ? "true" : "false"),selected:\(selectedUnigramIndex),\(unigrams))"
}
///
public var currentPair: KeyValuePaired {
selectedUnigramIndex >= unigrams.count ? KeyValuePaired() : candidates[selectedUnigramIndex]
}
///
public var highestUnigramScore: Double { unigrams.isEmpty ? 0.0 : unigrams[0].score }
///
/// - Parameters:
/// - key:
/// - unigrams:
/// - bigrams:
public init(key: String = "", spanLength: Int = 0, unigrams: [Megrez.Unigram] = [], bigrams: [Megrez.Bigram] = []) {
self.key = key
self.unigrams = unigrams
self.bigrams = bigrams
self.spanLength = spanLength
self.unigrams.sort {
$0.score > $1.score
}
if !self.unigrams.isEmpty {
score = unigrams[0].score
}
for (i, gram) in self.unigrams.enumerated() {
valueUnigramIndexMap[gram.keyValue.value] = i
candidates.append(gram.keyValue)
}
for gram in bigrams.lazy.filter({ [self] in
precedingBigramMap.keys.contains($0.precedingKeyValue)
}) {
precedingBigramMap[gram.precedingKeyValue]?.append(gram)
}
}
///
/// - Parameters:
/// - precedingKeyValues:
public func primeNodeWith(precedingKeyValues: [KeyValuePaired]) {
var newIndex = selectedUnigramIndex
var max = score
if !isCandidateFixed {
for neta in precedingKeyValues {
let bigrams = precedingBigramMap[neta] ?? []
for bigram in bigrams.lazy.filter({ [self] in
$0.score > max && valueUnigramIndexMap.keys.contains($0.keyValue.value)
}) {
newIndex = valueUnigramIndexMap[bigram.keyValue.value] ?? newIndex
max = bigram.score
}
}
}
score = max
selectedUnigramIndex = newIndex
}
///
/// - Parameters:
/// - index:
/// - fix:
public func selectCandidateAt(index: Int = 0, fix: Bool = false) {
let index = abs(index)
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = fix
score = Megrez.Node.kSelectedCandidateScore
}
///
public func resetCandidate() {
selectedUnigramIndex = 0
isCandidateFixed = false
if !unigrams.isEmpty {
score = unigrams[0].score
}
}
///
/// - Parameters:
/// - index:
/// - score:
public func selectFloatingCandidateAt(index: Int, score: Double) {
let index = abs(index) //
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = false
self.score = score
}
///
/// - Parameters:
/// - candidate:
public func scoreFor(candidate: String) -> Double {
for unigram in unigrams.lazy.filter({ $0.keyValue.value == candidate }) {
return unigram.score
}
return 0.0
}
///
/// - Parameters:
/// - candidate:
public func scoreForPaired(candidate: KeyValuePaired) -> Double {
for unigram in unigrams.lazy.filter({ $0.keyValue == candidate }) {
return unigram.score
}
return 0.0
}
}
}

View File

@ -0,0 +1,96 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
public class Span {
private var nodes: [Node?] = []
private(set) var maxLength = 0
private var maxSpanLength: Int { Megrez.Compositor.maxSpanLength }
public init() {
clear()
}
public func clear() {
nodes.removeAll()
for _ in 0..<maxSpanLength {
nodes.append(nil)
}
maxLength = 0
}
///
/// - Parameter node:
/// - Returns:
@discardableResult public func append(node: Node) -> Bool {
guard (1...maxSpanLength).contains(node.spanLength) else {
return false
}
nodes[node.spanLength - 1] = node
maxLength = max(maxLength, node.spanLength)
return true
}
///
/// - Parameter length:
/// - Returns:
@discardableResult public func dropNodesOfOrBeyond(length: Int) -> Bool {
guard (1...maxSpanLength).contains(length) else {
return false
}
for i in length...maxSpanLength {
nodes[i - 1] = nil
}
maxLength = 0
guard length > 1 else { return false }
let maxR = length - 2
for i in 0...maxR {
if nodes[maxR - i] != nil {
maxLength = maxR - i + 1
break
}
}
return true
}
public func nodeOf(length: Int) -> Node? {
guard (1...maxSpanLength).contains(length) else { return nil }
return nodes[length - 1] ?? nil
}
}
// MARK: Internal implementations.
///
/// - Parameter location:
/// - Returns:
func fetchOverlappingNodes(at location: Int) -> [NodeAnchor] {
var results = [NodeAnchor]()
guard !spans.isEmpty, location < spans.count else { return results }
//
for theLocation in 1...spans[location].maxLength {
guard let node = spans[location].nodeOf(length: theLocation) else { continue }
results.append(.init(node: node, spanIndex: location))
}
//
let begin: Int = location - min(location, Megrez.Compositor.maxSpanLength - 1)
for theLocation in begin..<location {
let (A, B): (Int, Int) = {
(
min(location - theLocation + 1, spans[theLocation].maxLength),
max(location - theLocation + 1, spans[theLocation].maxLength)
)
}()
for theLength in A...B {
guard let node = spans[theLocation].nodeOf(length: theLength) else { continue }
results.append(.init(node: node, spanIndex: theLocation))
}
}
return results
}
}

View File

@ -1,43 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
public protocol LangModelProtocol {
///
func unigramsFor(key: String) -> [Megrez.Unigram]
///
func bigramsFor(precedingKey: String, key: String) -> [Megrez.Bigram]
///
func hasUnigramsFor(key: String) -> Bool
}
extension Megrez {
/// 使
open class LangModel: LangModelProtocol {
public init() {}
// Swift
///
open func unigramsFor(key: String) -> [Megrez.Unigram] {
key.isEmpty ? [Megrez.Unigram]() : [Megrez.Unigram]()
}
///
open func bigramsFor(precedingKey: String, key: String) -> [Megrez.Bigram] {
precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]()
}
///
open func hasUnigramsFor(key: String) -> Bool {
key.count != 0
}
}
}

View File

@ -0,0 +1,96 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
///
///
class Vertex {
///
public var prev: Vertex?
///
public var edges = [Vertex]()
///
///
///
/// /
public var distance = -(Double.infinity)
///
public var topologicallySorted = false
public var node: Node
public init(node: Node) {
self.node = node
}
}
///
///
/// (relax) Cormen 2001 Introduction to Algorithms 585
/// - Parameters:
/// - u: v
/// - v:
func relax(u: Vertex, v: inout Vertex) {
/// u w v
let w: Double = v.node.score
///
/// v u ww u w v
/// v
if v.distance < u.distance + w {
v.distance = u.distance + w
v.prev = u
}
}
typealias VertexSpan = [Vertex]
/// topological
/// sort
///
/// 使
///
/// ```
/// func topologicalSort(vertex: Vertex) {
/// for vertexNode in vertex.edges {
/// if !vertexNode.topologicallySorted {
/// dfs(vertexNode, result)
/// vertexNode.topologicallySorted = true
/// }
/// result.append(vertexNode)
/// }
/// }
/// ```
/// Cormen 2001 Introduction to Algorithms
/// - Parameter root:
/// - Returns:
func topologicalSort(root: Vertex) -> [Vertex] {
class State {
var iterIndex: Int
var vertex: Vertex
init(vertex: Vertex, iterIndex: Int = 0) {
self.vertex = vertex
self.iterIndex = iterIndex
}
}
var result = [Vertex]()
var stack = [State]()
stack.append(.init(vertex: root))
while !stack.isEmpty {
let state = stack[stack.count - 1]
let theVertex = state.vertex
if state.iterIndex < state.vertex.edges.count {
let newVertex = state.vertex.edges[state.iterIndex]
state.iterIndex += 1
if !newVertex.topologicallySorted {
stack.append(.init(vertex: newVertex))
continue
}
}
theVertex.topologicallySorted = true
result.append(theVertex)
stack.removeLast()
}
return result
}
}

View File

@ -1,64 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct Bigram: Equatable, CustomStringConvertible, Hashable {
///
public var keyValue: KeyValuePaired
///
public var precedingKeyValue: KeyValuePaired
///
public var score: Double
///
public var description: String {
"(" + keyValue.description + "|" + precedingKeyValue.description + "," + String(score) + ")"
}
///
/// - Parameters:
/// - precedingKeyValue:
/// - keyValue:
/// - score:
public init(precedingKeyValue: KeyValuePaired, keyValue: KeyValuePaired, score: Double) {
self.keyValue = keyValue
self.precedingKeyValue = precedingKeyValue
self.score = score
}
public func hash(into hasher: inout Hasher) {
hasher.combine(keyValue)
hasher.combine(precedingKeyValue)
hasher.combine(score)
// hasher.combine(paired)
}
public static func == (lhs: Bigram, rhs: Bigram) -> Bool {
lhs.precedingKeyValue == rhs.precedingKeyValue && lhs.keyValue == rhs.keyValue && lhs.score == rhs.score
}
public static func < (lhs: Bigram, rhs: Bigram) -> Bool {
lhs.precedingKeyValue < rhs.precedingKeyValue
|| (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score))
}
}
}
// MARK: - DumpDOT-related functions.
extension Array where Element == Megrez.Bigram {
///
public var description: String {
var arrOutputContent = [""]
for (index, gram) in enumerated() {
arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description])
}
return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
}
}

View File

@ -0,0 +1,142 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez.Compositor {
///
///
///
///
/// 2
public class Node: Equatable, Hashable {
///
/// - withNoOverrides:
/// - withTopUnigramScore: 使使
///
/// [("a", -114), ("b", -514), ("c", -1919)]
/// ("c", -114)使
///
/// kOverridingScore
/// - withHighScore: kOverridingScore使
public enum OverrideType: Int {
case withNoOverrides = 0
case withTopUnigramScore = 1
case withHighScore = 2
}
///
/// 0使
/// a b cA B C使
/// c bc
/// A->bc A B 使0
/// A-B 0
/// c
public static let kOverridingScore: Double = 114_514
private(set) var key: String
private(set) var keyArray: [String]
private(set) var spanLength: Int
private(set) var unigrams: [Megrez.Unigram]
private(set) var currentUnigramIndex: Int = 0 {
didSet { currentUnigramIndex = min(max(0, currentUnigramIndex), unigrams.count - 1) }
}
public var currentPair: Megrez.Compositor.Candidate { .init(key: key, value: value) }
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(spanLength)
hasher.combine(unigrams)
hasher.combine(currentUnigramIndex)
hasher.combine(spanLength)
hasher.combine(overrideType)
}
private(set) var overrideType: Node.OverrideType
public static func == (lhs: Node, rhs: Node) -> Bool {
lhs.key == rhs.key && lhs.spanLength == rhs.spanLength
&& lhs.unigrams == rhs.unigrams && lhs.overrideType == rhs.overrideType
}
public init(
keyArray: [String] = [], spanLength: Int = 0, unigrams: [Megrez.Unigram] = [], keySeparator: String = ""
) {
key = keyArray.joined(separator: keySeparator)
self.keyArray = keyArray
self.spanLength = spanLength
self.unigrams = unigrams
overrideType = .withNoOverrides
}
///
/// - Returns:
public var currentUnigram: Megrez.Unigram {
unigrams.isEmpty ? .init() : unigrams[currentUnigramIndex]
}
public var value: String { currentUnigram.value }
public var score: Double {
guard !unigrams.isEmpty else { return 0 }
switch overrideType {
case .withHighScore: return Megrez.Compositor.Node.kOverridingScore
case .withTopUnigramScore: return unigrams[0].score
default: return currentUnigram.score
}
}
public var isOverriden: Bool {
overrideType != .withNoOverrides
}
public func reset() {
currentUnigramIndex = 0
overrideType = .withNoOverrides
}
public func selectOverrideUnigram(value: String, type: Node.OverrideType) -> Bool {
guard type != .withNoOverrides else {
return false
}
for (i, gram) in unigrams.enumerated() {
if value != gram.value { continue }
currentUnigramIndex = i
overrideType = type
return true
}
return false
}
}
}
extension Megrez.Compositor {
///
///
/// Gramambular NodeInSpan
public struct NodeAnchor: Hashable {
let node: Megrez.Compositor.Node
let spanIndex: Int //
var spanLength: Int { node.spanLength }
var unigrams: [Megrez.Unigram] { node.unigrams }
var key: String { node.key }
var value: String { node.value }
///
public func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(spanIndex)
}
}
}
// MARK: - Array Extensions.
extension Array where Element == Megrez.Compositor.Node {
///
public var values: [String] { map(\.value) }
///
public var keys: [String] { map(\.key) }
}

View File

@ -1,57 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct Unigram: Equatable, CustomStringConvertible, Hashable {
///
public var keyValue: KeyValuePaired
///
public var score: Double
///
public var description: String {
"(" + keyValue.description + "," + String(score) + ")"
}
///
/// - Parameters:
/// - keyValue:
/// - score:
public init(keyValue: KeyValuePaired, score: Double) {
self.keyValue = keyValue
self.score = score
}
public func hash(into hasher: inout Hasher) {
hasher.combine(keyValue)
hasher.combine(score)
}
public static func == (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.keyValue == rhs.keyValue && lhs.score == rhs.score
}
public static func < (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score)
}
}
}
// MARK: - DumpDOT-related functions.
extension Array where Element == Megrez.Unigram {
///
public var description: String {
var arrOutputContent = [""]
for (index, gram) in enumerated() {
arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description])
}
return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
}
}

View File

@ -1,58 +0,0 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension Megrez {
///
@frozen public struct KeyValuePaired: Equatable, Hashable, Comparable, CustomStringConvertible {
///
public var key: String
///
public var value: String
///
public var description: String { "(" + key + "," + value + ")" }
/// false
public var isValid: Bool { !key.isEmpty && !value.isEmpty }
/// ()
public var toNGramKey: String { !isValid ? "()" : "(" + key + "," + value + ")" }
///
/// - Parameters:
/// - key:
/// - value:
public init(key: String = "", value: String = "") {
self.key = key
self.value = value
}
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(value)
}
public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
lhs.key == rhs.key && lhs.value == rhs.value
}
public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count < rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value < rhs.value)
}
public static func > (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count > rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value > rhs.value)
}
public static func <= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count <= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value <= rhs.value)
}
public static func >= (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
(lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value)
}
}
}

View File

@ -0,0 +1,61 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
///
public protocol LangModelProtocol {
///
func unigramsFor(key: String) -> [Megrez.Unigram]
///
func hasUnigramsFor(key: String) -> Bool
}
extension Megrez.Compositor {
///
public class LangModelRanked: LangModelProtocol {
private let langModel: LangModelProtocol
///
/// - Parameter withLM:
public init(withLM: LangModelProtocol) {
langModel = withLM
}
///
/// - Parameter key:
/// - Returns:
public func unigramsFor(key: String) -> [Megrez.Unigram] {
langModel.unigramsFor(key: key).stableSorted { $0.score > $1.score }
}
///
/// - Parameter key:
/// - Returns:
public func hasUnigramsFor(key: String) -> Bool {
langModel.hasUnigramsFor(key: key)
}
}
}
// MARK: - Stable Sort Extension
// Reference: https://stackoverflow.com/a/50545761/4162914
extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
fileprivate func stableSorted(
by areInIncreasingOrder: (Element, Element) throws -> Bool
)
rethrows -> [Element]
{
try enumerated()
.sorted { a, b -> Bool in
try areInIncreasingOrder(a.element, b.element)
|| (a.offset < b.offset && !areInIncreasingOrder(b.element, a.element))
}
.map(\.element)
}
}

View File

@ -0,0 +1,40 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
extension Megrez {
///
@frozen public struct Unigram: Equatable, CustomStringConvertible, Hashable {
///
public var value: String
///
public var score: Double
///
public var description: String {
"(" + value.description + "," + String(score) + ")"
}
///
/// - Parameters:
/// - value:
/// - score:
public init(value: String = "", score: Double = 0) {
self.value = value
self.score = score
}
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
hasher.combine(score)
}
public static func == (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.value == rhs.value && lhs.score == rhs.score
}
public static func < (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.value < rhs.value || (lhs.value == rhs.value && lhs.score < rhs.score)
}
}
}

View File

@ -29,7 +29,7 @@
"\"%@\" length must ≥ 2 for a user phrase." = "\"%@\" length must ≥ 2 for a user phrase.";
"\"%@\" length should ≤ %d for a user phrase." = "\"%@\" length should ≤ %d for a user phrase.";
"\"%@\" selected. ENTER to add user phrase." = "\"%@\" selected. ENTER to add user phrase.";
"\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude." = "\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude.";
"\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude." = "\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude.";
"Edit Phrase Replacement Table…" = "Edit Phrase Replacement Table…";
"Use Phrase Replacement" = "Use Phrase Replacement";
"Candidates keys cannot be empty." = "Candidates keys cannot be empty.";
@ -113,6 +113,7 @@
"Choose or hit Enter to confim your prefered keys for selecting candidates." = "Choose or hit Enter to confim your prefered keys for selecting candidates.";
"Choose the behavior of (Shift+)Space key with candidates." = "Choose the behavior of (Shift+)Space key with candidates.";
"Choose the behavior of (Shift+)Tab key in the candidate window." = "Choose the behavior of (Shift+)Tab key in the candidate window.";
"Choose the behavior of Shift+Letter key with letter inputs." = "Choose the behavior of Shift+Letter key with letter inputs.";
"Choose the cursor position where you want to list possible candidates." = "Choose the cursor position where you want to list possible candidates.";
"Choose the macOS-level basic keyboard layout." = "Choose the macOS-level basic keyboard layout.";
"Choose the phonetic layout for Mandarin parser." = "Choose the phonetic layout for Mandarin parser.";
@ -123,6 +124,8 @@
"Dachen 26 (libChewing)" = "Dachen 26 (libChewing)";
"Debug Mode" = "Debug Mode";
"Dictionary" = "Dictionary";
"Directly commit lowercased letters" = "Directly commit lowercased letters";
"Directly commit uppercased letters" = "Directly commit uppercased letters";
"Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode";
"Enable CNS11643 Support (2022-07-20)" = "Enable CNS11643 Support (2022-07-20)";
"Enable Space key for calling candidate window" = "Enable Space key for calling candidate window";
@ -143,8 +146,8 @@
"IBM" = "IBM";
"in front of the phrase (like macOS built-in Zhuyin IME)" = "in front of the phrase (like macOS built-in Zhuyin IME)";
"Japanese" = "Japanese";
"Keyboard" = "Keyboard";
"Keyboard Shortcuts:" = "Keyboard Shortcuts:";
"Keyboard" = "Keyboard";
"Misc Settings:" = "Misc Settings:";
"MiTAC" = "MiTAC";
"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only.";
@ -155,6 +158,7 @@
"Secondary Pinyin with Numeral Intonation" = "Secondary Pinyin with Numeral Intonation";
"Seigyou" = "Seigyou (JinYei)";
"Selection Keys:" = "Selection Keys:";
"Shift+Letter:" = "Shift+Letter:";
"Show Hanyu-Pinyin in the inline composition buffer & tooltip" = "Show Hanyu-Pinyin in the inline composition buffer & tooltip";
"Show page buttons in candidate window" = "Show page buttons in candidate window";
"Simplified Chinese" = "Simplified Chinese";
@ -163,6 +167,7 @@
"Starlight" = "Starlight";
"Stop farting (when typed phonetic combination is invalid, etc.)" = "Stop farting (when typed phonetic combination is invalid, etc.)";
"Traditional Chinese" = "Traditional Chinese";
"Type them into inline composition buffer" = "Type them into inline composition buffer";
"Typing Style:" = "Typing Style:";
"UI Language:" = "UI Language:";
"Universal Pinyin with Numeral Intonation" = "Universal Pinyin with Numeral Intonation";

View File

@ -29,7 +29,7 @@
"\"%@\" length must ≥ 2 for a user phrase." = "\"%@\" length must ≥ 2 for a user phrase.";
"\"%@\" length should ≤ %d for a user phrase." = "\"%@\" length should ≤ %d for a user phrase.";
"\"%@\" selected. ENTER to add user phrase." = "\"%@\" selected. ENTER to add user phrase.";
"\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude." = "\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude.";
"\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude." = "\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude.";
"Edit Phrase Replacement Table…" = "Edit Phrase Replacement Table…";
"Use Phrase Replacement" = "Use Phrase Replacement";
"Candidates keys cannot be empty." = "Candidates keys cannot be empty.";
@ -113,6 +113,7 @@
"Choose or hit Enter to confim your prefered keys for selecting candidates." = "Choose or hit Enter to confim your prefered keys for selecting candidates.";
"Choose the behavior of (Shift+)Space key with candidates." = "Choose the behavior of (Shift+)Space key with candidates.";
"Choose the behavior of (Shift+)Tab key in the candidate window." = "Choose the behavior of (Shift+)Tab key in the candidate window.";
"Choose the behavior of Shift+Letter key with letter inputs." = "Choose the behavior of Shift+Letter key with letter inputs.";
"Choose the cursor position where you want to list possible candidates." = "Choose the cursor position where you want to list possible candidates.";
"Choose the macOS-level basic keyboard layout." = "Choose the macOS-level basic keyboard layout.";
"Choose the phonetic layout for Mandarin parser." = "Choose the phonetic layout for Mandarin parser.";
@ -123,6 +124,8 @@
"Dachen 26 (libChewing)" = "Dachen 26 (libChewing)";
"Debug Mode" = "Debug Mode";
"Dictionary" = "Dictionary";
"Directly commit lowercased letters" = "Directly commit lowercased letters";
"Directly commit uppercased letters" = "Directly commit uppercased letters";
"Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode";
"Enable CNS11643 Support (2022-07-20)" = "Enable CNS11643 Support (2022-07-20)";
"Enable Space key for calling candidate window" = "Enable Space key for calling candidate window";
@ -143,8 +146,8 @@
"IBM" = "IBM";
"in front of the phrase (like macOS built-in Zhuyin IME)" = "in front of the phrase (like macOS built-in Zhuyin IME)";
"Japanese" = "Japanese";
"Keyboard" = "Keyboard";
"Keyboard Shortcuts:" = "Keyboard Shortcuts:";
"Keyboard" = "Keyboard";
"Misc Settings:" = "Misc Settings:";
"MiTAC" = "MiTAC";
"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only.";
@ -155,6 +158,7 @@
"Secondary Pinyin with Numeral Intonation" = "Secondary Pinyin with Numeral Intonation";
"Seigyou" = "Seigyou (JinYei)";
"Selection Keys:" = "Selection Keys:";
"Shift+Letter:" = "Shift+Letter:";
"Show Hanyu-Pinyin in the inline composition buffer & tooltip" = "Show Hanyu-Pinyin in the inline composition buffer & tooltip";
"Show page buttons in candidate window" = "Show page buttons in candidate window";
"Simplified Chinese" = "Simplified Chinese";
@ -163,6 +167,7 @@
"Starlight" = "Starlight";
"Stop farting (when typed phonetic combination is invalid, etc.)" = "Stop farting (when typed phonetic combination is invalid, etc.)";
"Traditional Chinese" = "Traditional Chinese";
"Type them into inline composition buffer" = "Type them into inline composition buffer";
"Typing Style:" = "Typing Style:";
"UI Language:" = "UI Language:";
"Universal Pinyin with Numeral Intonation" = "Universal Pinyin with Numeral Intonation";

View File

@ -29,7 +29,7 @@
"\"%@\" length must ≥ 2 for a user phrase." = "「%@」もう1つ文字のお選びを。";
"\"%@\" length should ≤ %d for a user phrase." = "「%@」文字数過剰で登録不可、%d 文字以内にして下さい。";
"\"%@\" selected. ENTER to add user phrase." = "「%@」を ENTER で辞書に登録。";
"\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude." = "「%@」は既存語彙ENTER で最優先にし\n SHIFT+CMD+ENTER で排除。";
"\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」は既存語彙ENTER で最優先にし、SHIFT+CMD+ENTER で優先順位を下げる;\n BackSpace 或いは Delete で排除。";
"Edit Phrase Replacement Table…" = "言葉置換表を編集…";
"Use Phrase Replacement" = "言葉置換機能";
"Candidates keys cannot be empty." = "言選り用キー陣列に何かキーをご登録ください。";
@ -113,6 +113,7 @@
"Choose or hit Enter to confim your prefered keys for selecting candidates." = "お好きなる言選り用キー陣列をご指定ください。新しい組み合わせは Enter で効かす。";
"Choose the behavior of (Shift+)Space key with candidates." = "入力候補についての (Shift+)Space キーの輪番切替対象をご指定ください。";
"Choose the behavior of (Shift+)Tab key in the candidate window." = "入力候補陳列での (Shift+)Tab キーの輪番切替対象をご指定ください。";
"Choose the behavior of Shift+Letter key with letter inputs." = "Shift+文字キーの行為をご指定ください。";
"Choose the cursor position where you want to list possible candidates." = "カーソルはどこで入力候補を呼び出すかとご指定ださい。";
"Choose the macOS-level basic keyboard layout." = "macOS 基礎キーボード配置をご指定ください。";
"Choose the phonetic layout for Mandarin parser." = "共通語分析器の注音配列をご指定ください。";
@ -123,6 +124,8 @@
"Dachen 26 (libChewing)" = "酷音大千 26 キー配列";
"Debug Mode" = "欠陥辿着モード";
"Dictionary" = "辞書設定";
"Directly commit lowercased letters" = "ローマ字(小文字)を直接出力";
"Directly commit uppercased letters" = "ローマ字(大文字)を直接出力";
"Emulating select-candidate-per-character mode" = "漢字1つづつ全候補選択入力モード";
"Enable CNS11643 Support (2022-07-20)" = "全字庫モード // 入力可能な漢字数を倍増す (2022-07-20)";
"Enable Space key for calling candidate window" = "Space キーで入力候補を呼び出す";
@ -143,8 +146,8 @@
"IBM" = "IBM 配列";
"in front of the phrase (like macOS built-in Zhuyin IME)" = "単語の前で // macOS 内蔵注音入力のやり方";
"Japanese" = "和語";
"Keyboard" = "配列設定";
"Keyboard Shortcuts:" = "ショートカット:";
"Keyboard" = "配列設定";
"Misc Settings:" = "他の設定:";
"MiTAC" = "神通配列";
"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "QWERTY 以外の英数キーボードは漢語弁音以外の配列に不適用。";
@ -155,6 +158,7 @@
"Secondary Pinyin with Numeral Intonation" = "国音二式 (ローマ字+数字音調)";
"Seigyou" = "精業配列";
"Selection Keys:" = "言選り用キー:";
"Shift+Letter:" = "Shift+文字キー:";
"Show Hanyu-Pinyin in the inline composition buffer & tooltip" = "弁音合併入力(入力緩衝列とヒントで音読みを漢語弁音に)";
"Show page buttons in candidate window" = "入力候補陳列の側にページボタンを表示";
"Simplified Chinese" = "簡体中国語";
@ -163,6 +167,7 @@
"Starlight" = "星光配列";
"Stop farting (when typed phonetic combination is invalid, etc.)" = "マナーモード // 外すと入力間違った時に変な声が出る";
"Traditional Chinese" = "繁体中国語";
"Type them into inline composition buffer" = "入力緩衝列にローマ字入力";
"Typing Style:" = "入力習慣:";
"UI Language:" = "表示用言語:";
"Universal Pinyin with Numeral Intonation" = "汎用弁音 (ローマ字+数字音調)";

View File

@ -29,7 +29,7 @@
"\"%@\" length must ≥ 2 for a user phrase." = "「%@」字数不足以自订语汇。";
"\"%@\" length should ≤ %d for a user phrase." = "「%@」字数超过 %d、无法自订。";
"\"%@\" selected. ENTER to add user phrase." = "「%@」敲 Enter 添入自订语汇。";
"\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude." = "「%@」已存在:敲 Enter 以升权\n 敲 Shift+CMD+Enter 以排除。";
"\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」已存在:敲 Enter 以升权、敲 Shift+CMD+Enter 以降权;\n 敲 BackSpace 或 Delete 以排除。";
"Edit Phrase Replacement Table…" = "编辑语汇置换表…";
"Use Phrase Replacement" = "使用语汇置换";
"Candidates keys cannot be empty." = "您必须指定选字键。";
@ -113,6 +113,7 @@
"Choose or hit Enter to confim your prefered keys for selecting candidates." = "请选择您所偏好的用来选字的按键组合。自订组合需敲 Enter 键生效。";
"Choose the behavior of (Shift+)Space key with candidates." = "指定 (Shift+)空格键 对候选字词而言的轮替操作对象。";
"Choose the behavior of (Shift+)Tab key in the candidate window." = "指定 (Shift+)Tab 在选字窗内的轮替操作对象。";
"Choose the behavior of Shift+Letter key with letter inputs." = "指定 Shift+字母键 的行为。";
"Choose the cursor position where you want to list possible candidates." = "请选择用以触发选字的游标相对位置。";
"Choose the macOS-level basic keyboard layout." = "请选择 macOS 基础键盘布局。";
"Choose the phonetic layout for Mandarin parser." = "请指定普通话/国音分析器所使用的注音排列。";
@ -123,6 +124,8 @@
"Dachen 26 (libChewing)" = "酷音大千二十六键排列";
"Debug Mode" = "侦错模式";
"Dictionary" = "辞典";
"Directly commit lowercased letters" = "直接递交小写字母";
"Directly commit uppercased letters" = "直接递交大写字母";
"Emulating select-candidate-per-character mode" = "模拟 90 年代前期注音逐字选字输入风格";
"Enable CNS11643 Support (2022-07-20)" = "启用 CNS11643 全字库支援 (2022-07-20)";
"Enable Space key for calling candidate window" = "敲空格键以呼出候选字窗";
@ -144,8 +147,8 @@
"IBM" = "IBM 排列";
"in front of the phrase (like macOS built-in Zhuyin IME)" = "将游标置于词语前方 // macOS 内建注音风格";
"Japanese" = "和语";
"Keyboard" = "键盘";
"Keyboard Shortcuts:" = "键盘快捷键:";
"Keyboard" = "键盘";
"Misc Settings:" = "杂项:";
"MiTAC" = "神通排列";
"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "QWERTY 以外的英数布局是为了汉语拼音排列使用者而准备的。";
@ -156,6 +159,7 @@
"Secondary Pinyin with Numeral Intonation" = "国音二式+数字标调";
"Seigyou" = "精业排列";
"Selection Keys:" = "选字键:";
"Shift+Letter:" = "Shift+字母键:";
"Show Hanyu-Pinyin in the inline composition buffer & tooltip" = "拼音并击(组字区与工具提示内显示汉语拼音)";
"Show page buttons in candidate window" = "在选字窗内显示翻页按钮";
"Simplified Chinese" = "简体中文";
@ -164,6 +168,7 @@
"Starlight" = "星光排列";
"Stop farting (when typed phonetic combination is invalid, etc.)" = "廉耻模式 // 取消勾选的话,敲错字时会有异音";
"Traditional Chinese" = "繁体中文";
"Type them into inline composition buffer" = "直接键入内文组字区";
"Typing Style:" = "输入风格:";
"UI Language:" = "介面语言:";
"Universal Pinyin with Numeral Intonation" = "通用拼音+数字标调";

View File

@ -29,7 +29,7 @@
"\"%@\" length must ≥ 2 for a user phrase." = "「%@」字數不足以自訂語彙。";
"\"%@\" length should ≤ %d for a user phrase." = "「%@」字數超過 %d、無法自訂。";
"\"%@\" selected. ENTER to add user phrase." = "「%@」敲 Enter 添入自訂語彙。";
"\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude." = "「%@」已存在:敲 Enter 以升權\n 敲 Shift+CMD+Enter 以排除。";
"\"%@\" already exists: ENTER to boost, SHIFT+CMD+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」已存在:敲 Enter 以升權、敲 Shift+CMD+Enter 以降權;\n 敲 BackSpace 或 Delete 以排除。";
"Edit Phrase Replacement Table…" = "編輯語彙置換表…";
"Use Phrase Replacement" = "使用語彙置換";
"Candidates keys cannot be empty." = "您必須指定選字鍵。";
@ -113,6 +113,7 @@
"Choose or hit Enter to confim your prefered keys for selecting candidates." = "請選擇您所偏好的用來選字的按鍵組合。自訂組合需敲 Enter 鍵生效。";
"Choose the behavior of (Shift+)Space key with candidates." = "指定 (Shift+)空格鍵 對候選字詞而言的輪替操作對象。";
"Choose the behavior of (Shift+)Tab key in the candidate window." = "指定 (Shift+)Tab 在選字窗內的輪替操作對象。";
"Choose the behavior of Shift+Letter key with letter inputs." = "指定 Shift+字母鍵 的行為。";
"Choose the cursor position where you want to list possible candidates." = "請選擇用以觸發選字的游標相對位置。";
"Choose the macOS-level basic keyboard layout." = "請選擇 macOS 基礎鍵盤佈局。";
"Choose the phonetic layout for Mandarin parser." = "請指定普通話/國音分析器所使用的注音排列。";
@ -123,6 +124,8 @@
"Dachen 26 (libChewing)" = "酷音大千二十六鍵排列";
"Debug Mode" = "偵錯模式";
"Dictionary" = "辭典";
"Directly commit lowercased letters" = "直接遞交小寫字母";
"Directly commit uppercased letters" = "直接遞交大寫字母";
"Emulating select-candidate-per-character mode" = "模擬 90 年代前期注音逐字選字輸入風格";
"Enable CNS11643 Support (2022-07-20)" = "啟用 CNS11643 全字庫支援 (2022-07-20)";
"Enable Space key for calling candidate window" = "敲空格鍵以呼出候選字窗";
@ -143,8 +146,8 @@
"IBM" = "IBM 排列";
"in front of the phrase (like macOS built-in Zhuyin IME)" = "將游標置於詞語前方 // macOS 內建注音風格";
"Japanese" = "和語";
"Keyboard" = "鍵盤";
"Keyboard Shortcuts:" = "鍵盤快速鍵:";
"Keyboard" = "鍵盤";
"Misc Settings:" = "雜項:";
"MiTAC" = "神通排列";
"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "QWERTY 以外的英數佈局是為了漢語拼音排列使用者而準備的。";
@ -155,6 +158,7 @@
"Secondary Pinyin with Numeral Intonation" = "國音二式+數字標調";
"Seigyou" = "精業排列";
"Selection Keys:" = "選字鍵:";
"Shift+Letter:" = "Shift+字母鍵:";
"Show Hanyu-Pinyin in the inline composition buffer & tooltip" = "拼音並擊(組字區與工具提示內顯示漢語拼音)";
"Show page buttons in candidate window" = "在選字窗內顯示翻頁按鈕";
"Simplified Chinese" = "簡體中文";
@ -163,6 +167,7 @@
"Starlight" = "星光排列";
"Stop farting (when typed phonetic combination is invalid, etc.)" = "廉恥模式 // 取消勾選的話,敲錯字時會有異音";
"Traditional Chinese" = "繁體中文";
"Type them into inline composition buffer" = "直接鍵入內文組字區";
"Typing Style:" = "輸入風格:";
"UI Language:" = "介面語言:";
"Universal Pinyin with Numeral Intonation" = "通用拼音+數字標調";

View File

@ -37,6 +37,8 @@ struct suiPrefPaneExperience: View {
forKey: UserDef.kKeepReadingUponCompositionError.rawValue)
@State private var selTogglingAlphanumericalModeWithLShift = UserDefaults.standard.bool(
forKey: UserDef.kTogglingAlphanumericalModeWithLShift.rawValue)
@State private var selUpperCaseLetterKeyBehavior = UserDefaults.standard.integer(
forKey: UserDef.kUpperCaseLetterKeyBehavior.rawValue)
private let contentWidth: Double = {
switch mgrPrefs.appleLanguages[0] {
case "ja":
@ -123,6 +125,19 @@ struct suiPrefPaneExperience: View {
Text(LocalizedStringKey("Choose the behavior of (Shift+)Space key with candidates."))
.preferenceDescription()
}
Preferences.Section(label: { Text(LocalizedStringKey("Shift+Letter:")) }) {
Picker("", selection: $selUpperCaseLetterKeyBehavior) {
Text(LocalizedStringKey("Type them into inline composition buffer")).tag(0)
Text(LocalizedStringKey("Directly commit lowercased letters")).tag(1)
Text(LocalizedStringKey("Directly commit uppercased letters")).tag(2)
}.onChange(of: selUpperCaseLetterKeyBehavior) { value in
mgrPrefs.upperCaseLetterKeyBehavior = value
}
.labelsHidden()
.pickerStyle(RadioGroupPickerStyle())
Text(LocalizedStringKey("Choose the behavior of Shift+Letter key with letter inputs."))
.preferenceDescription()
}
Preferences.Section(label: { Text(LocalizedStringKey("Misc Settings:")) }) {
Toggle(
LocalizedStringKey("Enable Space key for calling candidate window"),

View File

@ -28,7 +28,7 @@
<window title="vChewing Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" animationBehavior="default" titlebarAppearsTransparent="YES" id="1" userLabel="frmPrefWindow">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" fullSizeContentView="YES"/>
<rect key="contentRect" x="401" y="295" width="445" height="501"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
<view key="contentView" id="2" customClass="NSVisualEffectView">
<rect key="frame" x="0.0" y="0.0" width="445" height="501"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -36,11 +36,11 @@
<point key="canvasLocation" x="-896.5" y="-1190"/>
</window>
<visualEffectView blendingMode="behindWindow" material="sidebar" state="followsWindowActiveState" id="BUt-lg-GPp" userLabel="vwrGeneral">
<rect key="frame" x="0.0" y="0.0" width="445" height="423"/>
<rect key="frame" x="0.0" y="0.0" width="445" height="405"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="Ldp-U1-36g">
<rect key="frame" x="18" y="388" width="379" height="15"/>
<rect key="frame" x="18" y="370" width="379" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Change UI font size of candidate window for a better visual clarity." id="iRg-wx-Nx2">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -48,7 +48,7 @@
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="28">
<rect key="frame" x="32" y="363" width="133" height="16"/>
<rect key="frame" x="32" y="345" width="133" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="Qo1-CW-mbl"/>
</constraints>
@ -59,7 +59,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="5Rz-c8-hp9">
<rect key="frame" x="18" y="339" width="398" height="15"/>
<rect key="frame" x="18" y="321" width="398" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Change user interface language (will reboot the IME)." id="ZEv-Q2-mYL">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -67,7 +67,7 @@
</textFieldCell>
</textField>
<popUpButton wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oS6-u5-7dP">
<rect key="frame" x="167" y="306" width="232" height="25"/>
<rect key="frame" x="167" y="288" width="232" height="25"/>
<popUpButtonCell key="cell" type="push" title="Auto-Select" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="GlJ-Ns-9eE" id="bft-Wv-kiJ">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="cellTitle"/>
@ -86,7 +86,7 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="249" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="L5M-VG-W1X">
<rect key="frame" x="47" y="314" width="118" height="16"/>
<rect key="frame" x="47" y="296" width="118" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="UI language setting:" id="9DS-Rc-TXq">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -94,7 +94,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="749" translatesAutoresizingMaskIntoConstraints="NO" id="Yl4-Ar-L6r">
<rect key="frame" x="18" y="281" width="398" height="21"/>
<rect key="frame" x="18" y="265" width="398" height="19"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose your preferred layout of the candidate window." id="xC5-yV-1W1">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -102,7 +102,7 @@
</textFieldCell>
</textField>
<matrix wantsLayer="YES" verticalHuggingPriority="750" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="19">
<rect key="frame" x="169" y="257" width="210" height="18"/>
<rect key="frame" x="169" y="241" width="210" height="18"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="103" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
@ -129,7 +129,7 @@
</connections>
</matrix>
<button verticalHuggingPriority="750" id="233">
<rect key="frame" x="169" y="235.5" width="245" height="17"/>
<rect key="frame" x="169" y="219.5" width="245" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Show page buttons in candidate list" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="shc-Nu-UsM">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
@ -140,7 +140,7 @@
</buttonCell>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="da1-7e-els">
<rect key="frame" x="18" y="215" width="246" height="15"/>
<rect key="frame" x="18" y="199" width="246" height="15"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="242" id="PV1-f3-Tks"/>
</constraints>
@ -151,7 +151,10 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5IL-zZ-CL9" userLabel="chkTrad2KangXi">
<rect key="frame" x="19" y="184.5" width="401" height="16"/>
<rect key="frame" x="19" y="173.5" width="401" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="leQ-wY-RJ3"/>
</constraints>
<buttonCell key="cell" type="check" title="Auto-convert traditional Chinese glyphs to KangXi characters" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="BSK-bH-Gct">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
@ -162,7 +165,10 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="h4r-Sp-LBI" userLabel="chkTrad2JISShinjitai">
<rect key="frame" x="19" y="163.5" width="401" height="16"/>
<rect key="frame" x="19" y="152.5" width="401" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="98P-se-n98"/>
</constraints>
<buttonCell key="cell" type="check" title="Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="eia-1F-Do0">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
@ -173,7 +179,10 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pYB-E5-4Nv">
<rect key="frame" x="19" y="142.5" width="401" height="16"/>
<rect key="frame" x="19" y="131.5" width="401" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="vh4-0M-mbd"/>
</constraints>
<buttonCell key="cell" type="check" title="Stop farting (when typed phonetic combination is invalid, etc.)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="62u-jY-BRh">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
@ -184,7 +193,10 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vwf-Kq-s8M" userLabel="chkShowHanyuPinyinInCompositionBuffer">
<rect key="frame" x="19" y="121.5" width="401" height="16"/>
<rect key="frame" x="19" y="110.5" width="401" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="6X9-Ph-39L"/>
</constraints>
<buttonCell key="cell" type="check" title="Show Hanyu-Pinyin in the inline composition buffer &amp; tooltip" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="wFR-zX-M8H">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
@ -195,7 +207,10 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gZ0-OK-r7a" userLabel="InlineDumpPinyinInLieuOfZhuyin">
<rect key="frame" x="19" y="100.5" width="401" height="16"/>
<rect key="frame" x="19" y="89.5" width="401" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="zRq-Om-0dK"/>
</constraints>
<buttonCell key="cell" type="check" title="Output Hanyu-Pinyin in lieu of Zhuyin when Ctrl(+Alt)+CMD+Enter" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="iWy-Nw-QKB">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
@ -206,41 +221,18 @@
</connections>
</button>
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="E66-0o-wJZ">
<rect key="frame" x="20" y="79" width="405" height="5"/>
<rect key="frame" x="20" y="78" width="405" height="5"/>
</box>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="23">
<rect key="frame" x="34" y="259" width="131" height="15"/>
<rect key="frame" x="34" y="243" width="131" height="15"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Candidate List Layout:" id="24">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="Fc2-qh-r1H">
<rect key="frame" x="19" y="52.5" width="406" height="16"/>
<buttonCell key="cell" type="check" title="Check for updates automatically" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="Z9t-P0-BLF">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.CheckUpdateAutomatically" id="6WP-5h-sHG"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mwD-Yz-AU9">
<rect key="frame" x="19" y="30.5" width="94" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="4gB-2r-hvg"/>
</constraints>
<buttonCell key="cell" type="check" title="Debug Mode" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="sZx-18-8dO">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values._DebugMode" id="RsM-TR-7Lf"/>
</connections>
</button>
<popUpButton wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="90">
<rect key="frame" x="166" y="358" width="87" height="22"/>
<rect key="frame" x="166" y="340" width="87" height="22"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="79" id="yrv-1o-vuR"/>
</constraints>
@ -264,74 +256,97 @@
<binding destination="32" name="selectedTag" keyPath="values.CandidateListTextSize" id="107"/>
</connections>
</popUpButton>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0Fh-O5-BKD">
<rect key="frame" x="19" y="26.5" width="94" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="3qm-fU-ab5"/>
</constraints>
<buttonCell key="cell" type="check" title="Debug Mode" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="VdT-fw-7pQ">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values._DebugMode" id="sEa-DE-7Dw"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="ChK-Du-6h1">
<rect key="frame" x="19" y="45.5" width="406" height="21"/>
<buttonCell key="cell" type="check" title="Check for updates automatically" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="8tT-hw-Hhr">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.CheckUpdateAutomatically" id="uaH-89-Mqc"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="19" firstAttribute="leading" secondItem="23" secondAttribute="trailing" constant="6" id="0zR-dL-yOF"/>
<constraint firstItem="5IL-zZ-CL9" firstAttribute="top" secondItem="da1-7e-els" secondAttribute="bottom" constant="15" id="1M7-17-863"/>
<constraint firstItem="oS6-u5-7dP" firstAttribute="top" secondItem="5Rz-c8-hp9" secondAttribute="bottom" constant="9" id="1p6-cW-UOJ"/>
<constraint firstItem="5Rz-c8-hp9" firstAttribute="top" secondItem="28" secondAttribute="bottom" constant="9" id="2yQ-gc-uaP"/>
<constraint firstItem="28" firstAttribute="top" secondItem="Ldp-U1-36g" secondAttribute="bottom" constant="9" id="3Uz-RD-Kop"/>
<constraint firstItem="gZ0-OK-r7a" firstAttribute="top" secondItem="vwf-Kq-s8M" secondAttribute="bottom" constant="6" symbolic="YES" id="3Zd-UB-3wy"/>
<constraint firstItem="5IL-zZ-CL9" firstAttribute="leading" secondItem="da1-7e-els" secondAttribute="leading" id="3jm-EX-6MU"/>
<constraint firstItem="Yl4-Ar-L6r" firstAttribute="trailing" secondItem="233" secondAttribute="trailing" id="61n-yX-hcq"/>
<constraint firstItem="ChK-Du-6h1" firstAttribute="top" secondItem="E66-0o-wJZ" secondAttribute="bottom" constant="14.5" id="67C-FL-scr"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="90" secondAttribute="trailing" constant="20" symbolic="YES" id="70T-45-wkx"/>
<constraint firstItem="da1-7e-els" firstAttribute="leading" secondItem="5IL-zZ-CL9" secondAttribute="leading" id="76z-fD-Wmi"/>
<constraint firstItem="Yl4-Ar-L6r" firstAttribute="top" secondItem="oS6-u5-7dP" secondAttribute="bottom" constant="8" symbolic="YES" id="7Qd-8S-h7n"/>
<constraint firstItem="E66-0o-wJZ" firstAttribute="leading" secondItem="Fc2-qh-r1H" secondAttribute="leading" id="8Lw-KC-KcH"/>
<constraint firstItem="5IL-zZ-CL9" firstAttribute="leading" secondItem="h4r-Sp-LBI" secondAttribute="leading" id="9jd-oS-zsY"/>
<constraint firstAttribute="trailing" secondItem="E66-0o-wJZ" secondAttribute="trailing" constant="20" symbolic="YES" id="7XN-CR-DQs"/>
<constraint firstItem="Ldp-U1-36g" firstAttribute="leading" secondItem="5Rz-c8-hp9" secondAttribute="leading" id="AJV-6w-zzb"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ldp-U1-36g" secondAttribute="trailing" constant="20" symbolic="YES" id="CGs-3y-UKf"/>
<constraint firstItem="gZ0-OK-r7a" firstAttribute="leading" secondItem="E66-0o-wJZ" secondAttribute="leading" id="COA-Gy-V6W"/>
<constraint firstItem="E66-0o-wJZ" firstAttribute="top" secondItem="gZ0-OK-r7a" secondAttribute="bottom" constant="19" id="FEO-KC-ere"/>
<constraint firstAttribute="bottom" secondItem="0Fh-O5-BKD" secondAttribute="bottom" constant="26.5" id="GNe-G9-GaS"/>
<constraint firstItem="da1-7e-els" firstAttribute="top" relation="lessThanOrEqual" secondItem="233" secondAttribute="bottom" constant="6" id="GPh-Pz-Koa"/>
<constraint firstItem="19" firstAttribute="top" secondItem="Yl4-Ar-L6r" secondAttribute="bottom" constant="6" id="HLS-hW-sXe"/>
<constraint firstItem="233" firstAttribute="top" secondItem="19" secondAttribute="bottom" constant="5" id="IRf-SV-AoA"/>
<constraint firstItem="pYB-E5-4Nv" firstAttribute="leading" secondItem="vwf-Kq-s8M" secondAttribute="leading" id="JHs-AR-h4Q"/>
<constraint firstItem="28" firstAttribute="bottom" secondItem="90" secondAttribute="bottom" constant="-1" id="Joq-Xd-5Rr"/>
<constraint firstItem="90" firstAttribute="leading" secondItem="28" secondAttribute="trailing" constant="7" id="NSs-y0-XCD"/>
<constraint firstItem="vwf-Kq-s8M" firstAttribute="trailing" secondItem="gZ0-OK-r7a" secondAttribute="trailing" id="RDI-vM-EEv"/>
<constraint firstItem="E66-0o-wJZ" firstAttribute="leading" secondItem="ChK-Du-6h1" secondAttribute="leading" id="O6Y-yf-T7l"/>
<constraint firstItem="h4r-Sp-LBI" firstAttribute="trailing" secondItem="pYB-E5-4Nv" secondAttribute="trailing" id="P5O-tf-9t3"/>
<constraint firstItem="gZ0-OK-r7a" firstAttribute="leading" secondItem="E66-0o-wJZ" secondAttribute="leading" id="QYV-SL-rOZ"/>
<constraint firstItem="5Rz-c8-hp9" firstAttribute="trailing" secondItem="Yl4-Ar-L6r" secondAttribute="trailing" id="RE6-sb-uIi"/>
<constraint firstItem="vwf-Kq-s8M" firstAttribute="leading" secondItem="gZ0-OK-r7a" secondAttribute="leading" id="RG2-5d-hZx"/>
<constraint firstAttribute="bottom" secondItem="mwD-Yz-AU9" secondAttribute="bottom" constant="31" id="SYP-TN-Ogk"/>
<constraint firstItem="pYB-E5-4Nv" firstAttribute="leading" secondItem="vwf-Kq-s8M" secondAttribute="leading" id="URW-WH-D3O"/>
<constraint firstItem="gZ0-OK-r7a" firstAttribute="top" secondItem="vwf-Kq-s8M" secondAttribute="bottom" constant="3" id="Uf2-Hp-TrN"/>
<constraint firstItem="L5M-VG-W1X" firstAttribute="trailing" secondItem="23" secondAttribute="trailing" id="UyJ-6A-kFX"/>
<constraint firstItem="28" firstAttribute="trailing" secondItem="L5M-VG-W1X" secondAttribute="trailing" id="VZ6-NJ-J4J"/>
<constraint firstAttribute="trailing" secondItem="E66-0o-wJZ" secondAttribute="trailing" constant="20" symbolic="YES" id="Vxt-er-nyM"/>
<constraint firstItem="pYB-E5-4Nv" firstAttribute="trailing" secondItem="vwf-Kq-s8M" secondAttribute="trailing" id="aq7-bA-juj"/>
<constraint firstItem="ChK-Du-6h1" firstAttribute="leading" secondItem="0Fh-O5-BKD" secondAttribute="leading" id="Vn0-NE-Ijg"/>
<constraint firstItem="vwf-Kq-s8M" firstAttribute="leading" secondItem="gZ0-OK-r7a" secondAttribute="leading" id="Vnq-83-Czq"/>
<constraint firstItem="E66-0o-wJZ" firstAttribute="top" secondItem="gZ0-OK-r7a" secondAttribute="bottom" constant="8.5" id="Z6e-kD-Enr"/>
<constraint firstItem="vwf-Kq-s8M" firstAttribute="top" secondItem="pYB-E5-4Nv" secondAttribute="bottom" constant="3" id="c9O-i2-You"/>
<constraint firstItem="23" firstAttribute="top" secondItem="Yl4-Ar-L6r" secondAttribute="bottom" constant="7" id="d0w-Pi-SfE"/>
<constraint firstItem="Fc2-qh-r1H" firstAttribute="top" secondItem="E66-0o-wJZ" secondAttribute="bottom" constant="13" id="d3J-EG-jnz"/>
<constraint firstItem="5Rz-c8-hp9" firstAttribute="leading" secondItem="Yl4-Ar-L6r" secondAttribute="leading" id="dXN-il-YDS"/>
<constraint firstItem="Fc2-qh-r1H" firstAttribute="leading" secondItem="mwD-Yz-AU9" secondAttribute="leading" id="eX8-jJ-vyc"/>
<constraint firstItem="h4r-Sp-LBI" firstAttribute="trailing" secondItem="pYB-E5-4Nv" secondAttribute="trailing" id="eyF-ME-nzz"/>
<constraint firstItem="h4r-Sp-LBI" firstAttribute="leading" secondItem="pYB-E5-4Nv" secondAttribute="leading" id="dYt-oX-dnt"/>
<constraint firstItem="23" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="BUt-lg-GPp" secondAttribute="leading" constant="20" symbolic="YES" id="fVo-zM-hxe"/>
<constraint firstAttribute="trailing" secondItem="5IL-zZ-CL9" secondAttribute="trailing" constant="25" id="fvN-Xu-sOg"/>
<constraint firstItem="h4r-Sp-LBI" firstAttribute="top" secondItem="5IL-zZ-CL9" secondAttribute="bottom" constant="3" id="gSm-FR-Bwq"/>
<constraint firstItem="90" firstAttribute="leading" secondItem="oS6-u5-7dP" secondAttribute="leading" id="h1Q-UC-WWd"/>
<constraint firstItem="vwf-Kq-s8M" firstAttribute="top" secondItem="pYB-E5-4Nv" secondAttribute="bottom" constant="6" id="iDF-SS-g0P"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="da1-7e-els" secondAttribute="trailing" constant="20" symbolic="YES" id="iM3-gf-fp7"/>
<constraint firstItem="L5M-VG-W1X" firstAttribute="top" secondItem="5Rz-c8-hp9" secondAttribute="bottom" constant="9" id="iZF-3e-q0g"/>
<constraint firstItem="L5M-VG-W1X" firstAttribute="baseline" secondItem="oS6-u5-7dP" secondAttribute="baseline" constant="-1" id="inK-mb-fEf"/>
<constraint firstItem="5IL-zZ-CL9" firstAttribute="top" secondItem="da1-7e-els" secondAttribute="bottom" constant="7.5" id="jBl-9u-Vsz"/>
<constraint firstItem="Ldp-U1-36g" firstAttribute="leading" secondItem="BUt-lg-GPp" secondAttribute="leading" constant="20" symbolic="YES" id="kQl-JA-uUs"/>
<constraint firstItem="h4r-Sp-LBI" firstAttribute="leading" secondItem="pYB-E5-4Nv" secondAttribute="leading" id="kqQ-oc-q9p"/>
<constraint firstItem="mwD-Yz-AU9" firstAttribute="top" secondItem="Fc2-qh-r1H" secondAttribute="bottom" constant="6" symbolic="YES" id="lho-1F-0Dc"/>
<constraint firstItem="E66-0o-wJZ" firstAttribute="trailing" secondItem="Fc2-qh-r1H" secondAttribute="trailing" id="m9Z-9m-aea"/>
<constraint firstItem="pYB-E5-4Nv" firstAttribute="trailing" secondItem="vwf-Kq-s8M" secondAttribute="trailing" id="ktr-n4-mOt"/>
<constraint firstItem="0Fh-O5-BKD" firstAttribute="top" secondItem="ChK-Du-6h1" secondAttribute="bottom" constant="1" id="lik-I8-J2r"/>
<constraint firstItem="L5M-VG-W1X" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="BUt-lg-GPp" secondAttribute="leading" constant="20" symbolic="YES" id="mLV-Ha-whl"/>
<constraint firstAttribute="trailing" secondItem="oS6-u5-7dP" secondAttribute="trailing" constant="50" id="mna-vi-YX6"/>
<constraint firstItem="5IL-zZ-CL9" firstAttribute="leading" secondItem="h4r-Sp-LBI" secondAttribute="leading" id="nYg-K3-khN"/>
<constraint firstItem="28" firstAttribute="top" secondItem="90" secondAttribute="top" constant="-1" id="nqJ-Se-hbc"/>
<constraint firstItem="h4r-Sp-LBI" firstAttribute="top" secondItem="5IL-zZ-CL9" secondAttribute="bottom" constant="6" symbolic="YES" id="p5Z-UY-wTl"/>
<constraint firstItem="Yl4-Ar-L6r" firstAttribute="leading" secondItem="da1-7e-els" secondAttribute="leading" id="pkm-6Y-iKY"/>
<constraint firstItem="pYB-E5-4Nv" firstAttribute="top" secondItem="h4r-Sp-LBI" secondAttribute="bottom" constant="6" symbolic="YES" id="rAH-bY-4Po"/>
<constraint firstItem="28" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="BUt-lg-GPp" secondAttribute="leading" constant="20" symbolic="YES" id="sd1-UX-zTn"/>
<constraint firstAttribute="trailing" secondItem="5IL-zZ-CL9" secondAttribute="trailing" constant="25" id="stS-6Z-JtD"/>
<constraint firstItem="vwf-Kq-s8M" firstAttribute="trailing" secondItem="gZ0-OK-r7a" secondAttribute="trailing" id="sdK-Rw-ysg"/>
<constraint firstItem="pYB-E5-4Nv" firstAttribute="top" secondItem="h4r-Sp-LBI" secondAttribute="bottom" constant="3" id="tjF-VE-m0y"/>
<constraint firstItem="E66-0o-wJZ" firstAttribute="trailing" secondItem="ChK-Du-6h1" secondAttribute="trailing" id="umf-Iu-qlT"/>
<constraint firstItem="oS6-u5-7dP" firstAttribute="leading" secondItem="233" secondAttribute="leading" id="wDW-xk-7CC"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="0Fh-O5-BKD" secondAttribute="trailing" constant="20" symbolic="YES" id="wfW-8p-61i"/>
<constraint firstItem="Ldp-U1-36g" firstAttribute="top" secondItem="BUt-lg-GPp" secondAttribute="top" constant="20" symbolic="YES" id="wgv-Qw-pjZ"/>
<constraint firstItem="5IL-zZ-CL9" firstAttribute="trailing" secondItem="h4r-Sp-LBI" secondAttribute="trailing" id="xBw-8D-sNC"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="mwD-Yz-AU9" secondAttribute="trailing" constant="20" symbolic="YES" id="yDf-pj-RJW"/>
<constraint firstItem="5IL-zZ-CL9" firstAttribute="trailing" secondItem="h4r-Sp-LBI" secondAttribute="trailing" id="zta-0r-hTY"/>
</constraints>
<point key="canvasLocation" x="-895.5" y="-636.5"/>
<point key="canvasLocation" x="-895.5" y="-645.5"/>
</visualEffectView>
<visualEffectView blendingMode="behindWindow" material="sidebar" state="followsWindowActiveState" id="XWo-36-xGi" userLabel="vwrExperience">
<rect key="frame" x="0.0" y="0.0" width="445" height="460"/>
<rect key="frame" x="0.0" y="0.0" width="445" height="552"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField autoresizesSubviews="NO" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="IpX-f7-rTL">
<rect key="frame" x="18" y="425" width="317" height="15"/>
<rect key="frame" x="18" y="517" width="317" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose which keys you prefer for selecting candidates." id="2pS-nv-te4">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -339,7 +354,7 @@
</textFieldCell>
</textField>
<comboBox verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uHU-aL-du7">
<rect key="frame" x="128" y="395" width="150" height="24"/>
<rect key="frame" x="127" y="487" width="151" height="23"/>
<constraints>
<constraint firstAttribute="width" constant="147" id="Luo-hb-kcY"/>
</constraints>
@ -358,7 +373,7 @@
</connections>
</comboBox>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ETa-09-qWI">
<rect key="frame" x="31" y="399" width="91" height="15"/>
<rect key="frame" x="31" y="491" width="91" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Selection Keys:" id="FnD-oH-El5">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -366,7 +381,7 @@
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="13">
<rect key="frame" x="18" y="373" width="403" height="15"/>
<rect key="frame" x="18" y="463" width="403" height="15"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Choose the cursor position where you want to list possible candidates." id="14">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -374,7 +389,7 @@
</textFieldCell>
</textField>
<matrix wantsLayer="YES" verticalHuggingPriority="751" tag="1" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="15">
<rect key="frame" x="33" y="327" width="402" height="38"/>
<rect key="frame" x="33" y="417" width="402" height="38"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="402" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
@ -399,7 +414,7 @@
</connections>
</matrix>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7z2-DD-c58">
<rect key="frame" x="33" y="306.5" width="318" height="16"/>
<rect key="frame" x="33" y="396.5" width="318" height="16"/>
<buttonCell key="cell" type="check" title="Push the cursor in front of the phrase after selection" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="RUG-ls-KyA">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
@ -408,8 +423,102 @@
<binding destination="32" name="value" keyPath="values.MoveCursorAfterSelectingCandidate" id="BbO-T6-zh3"/>
</connections>
</button>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TMn-LX-3Ub">
<rect key="frame" x="18" y="373" width="369" height="15"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Choose the behavior of (Shift+)Tab key in the candidate window." id="ueU-Rz-a1C">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="J0f-Aw-dxC">
<rect key="frame" x="18" y="325" width="336" height="15"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Choose the behavior of (Shift+)Space key with candidates." id="Pg5-G9-pY5">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bE0-Lq-Pj7">
<rect key="frame" x="19" y="141.5" width="266" height="16"/>
<buttonCell key="cell" type="check" title="Use ESC key to clear the entire input buffer" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="f2j-xD-4xK">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.EscToCleanInputBuffer" id="aLf-PK-mAS"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="109">
<rect key="frame" x="19" y="162.5" width="285" height="16"/>
<buttonCell key="cell" type="check" title="Enable Space key for calling candidate window" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="110">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.ChooseCandidateUsingSpace" id="NU1-DG-vUz"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mzw-F2-aAQ">
<rect key="frame" x="19" y="120.5" width="295" height="16"/>
<buttonCell key="cell" type="check" title="Emulating select-candidate-per-character mode" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="ArK-Vk-OoT">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.UseSCPCTypingMode" id="PbD-wq-OsN"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="j8R-Hj-3dj">
<rect key="frame" x="19" y="99.5" width="340" height="16"/>
<buttonCell key="cell" type="check" title="Automatically correct reading combinations when typing" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkAutoCorrectReadingCombination">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.AutoCorrectReadingCombination" id="bix-eJ-jtV"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6MM-WC-Mpd">
<rect key="frame" x="19" y="78.5" width="388" height="16"/>
<buttonCell key="cell" type="check" title="Allow using Enter key to confirm associated candidate selection" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkAlsoConfirmAssociatedCandidatesByEnter">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.AlsoConfirmAssociatedCandidatesByEnter" id="P1C-j9-N88"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="HaB-rc-AcW">
<rect key="frame" x="19" y="57.5" width="388" height="16"/>
<buttonCell key="cell" type="check" title="Allow backspace-editing miscomposed readings" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkKeepReadingUponCompositionError">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.KeepReadingUponCompositionError" id="ddF-qg-jes"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FCB-ax-ARZ">
<rect key="frame" x="19" y="36.5" width="388" height="16"/>
<buttonCell key="cell" type="check" title="Also toggle alphanumerical mode with Left-Shift" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkTogglingAlphanumericalModeWithLShift">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.TogglingAlphanumericalModeWithLShift" id="XW6-un-l8B"/>
</connections>
</button>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6q5-OP-iEb">
<rect key="frame" x="18" y="255" width="332" height="15"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Choose the behavior of Shift+Letter key with letter inputs." id="lblUpperCaseLetterKeyBehavior">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<matrix wantsLayer="YES" verticalHuggingPriority="750" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="n7q-ew-DYu">
<rect key="frame" x="33" y="258" width="352" height="18"/>
<rect key="frame" x="32" y="347" width="352" height="18"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="174" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
@ -435,16 +544,8 @@
<binding destination="32" name="selectedTag" keyPath="values.SpecifyShiftTabKeyBehavior" id="R1A-Ji-8i2"/>
</connections>
</matrix>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TMn-LX-3Ub">
<rect key="frame" x="18" y="283" width="369" height="15"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Choose the behavior of (Shift+)Tab key in the candidate window." id="ueU-Rz-a1C">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<matrix wantsLayer="YES" verticalHuggingPriority="750" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YkJ-lr-EP6">
<rect key="frame" x="33" y="190" width="386" height="38"/>
<rect key="frame" x="32" y="279" width="386" height="38"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="386" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
@ -468,100 +569,53 @@
<binding destination="32" name="selectedTag" keyPath="values.SpecifyShiftSpaceKeyBehavior" id="Ajq-3d-Lh1"/>
</connections>
</matrix>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="J0f-Aw-dxC">
<rect key="frame" x="18" y="235" width="336" height="15"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Choose the behavior of (Shift+)Space key with candidates." id="Pg5-G9-pY5">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bE0-Lq-Pj7">
<rect key="frame" x="19" y="144.5" width="266" height="16"/>
<buttonCell key="cell" type="check" title="Use ESC key to clear the entire input buffer" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="f2j-xD-4xK">
<matrix wantsLayer="YES" verticalHuggingPriority="750" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="veW-XM-HGs">
<rect key="frame" x="32" y="187" width="386" height="61"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="386" height="19"/>
<size key="intercellSpacing" width="4" height="2"/>
<buttonCell key="prototype" type="radio" title="Radio" imagePosition="left" alignment="left" controlSize="small" inset="2" id="Fxk-tK-cP7">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<cells>
<column>
<buttonCell type="radio" title="Type them into inline composition buffer" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="rdoUpperCaseLetterKeyBehavior0">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<buttonCell type="radio" title="Directly commit lowercased letters" imagePosition="left" alignment="left" controlSize="small" tag="1" inset="2" id="rdoUpperCaseLetterKeyBehavior1">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<buttonCell type="radio" title="Directly commit uppercased letters" imagePosition="left" alignment="left" controlSize="small" tag="2" inset="2" id="rdoUpperCaseLetterKeyBehavior2">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
</column>
</cells>
<connections>
<binding destination="32" name="value" keyPath="values.EscToCleanInputBuffer" id="aLf-PK-mAS"/>
<binding destination="32" name="selectedTag" keyPath="values.UpperCaseLetterKeyBehavior" id="fCQ-Ru-Fuq"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="109">
<rect key="frame" x="19" y="165.5" width="285" height="16"/>
<buttonCell key="cell" type="check" title="Enable Space key for calling candidate window" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="110">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.ChooseCandidateUsingSpace" id="NU1-DG-vUz"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mzw-F2-aAQ">
<rect key="frame" x="19" y="123.5" width="295" height="16"/>
<buttonCell key="cell" type="check" title="Emulating select-candidate-per-character mode" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="ArK-Vk-OoT">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.UseSCPCTypingMode" id="PbD-wq-OsN"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="j8R-Hj-3dj">
<rect key="frame" x="19" y="102.5" width="340" height="16"/>
<buttonCell key="cell" type="check" title="Automatically correct reading combinations when typing" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkAutoCorrectReadingCombination">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.AutoCorrectReadingCombination" id="bix-eJ-jtV"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6MM-WC-Mpd">
<rect key="frame" x="19" y="81.5" width="388" height="16"/>
<buttonCell key="cell" type="check" title="Allow using Enter key to confirm associated candidate selection" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkAlsoConfirmAssociatedCandidatesByEnter">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.AlsoConfirmAssociatedCandidatesByEnter" id="P1C-j9-N88"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="HaB-rc-AcW">
<rect key="frame" x="19" y="60.5" width="388" height="16"/>
<buttonCell key="cell" type="check" title="Allow backspace-editing miscomposed readings" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkKeepReadingUponCompositionError">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.KeepReadingUponCompositionError" id="ddF-qg-jes"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FCB-ax-ARZ">
<rect key="frame" x="19" y="39.5" width="388" height="16"/>
<buttonCell key="cell" type="check" title="Also toggle alphanumerical mode with Left-Shift" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="chkTogglingAlphanumericalModeWithLShift">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.TogglingAlphanumericalModeWithLShift" id="XW6-un-l8B"/>
</connections>
</button>
</matrix>
</subviews>
<constraints>
<constraint firstItem="n7q-ew-DYu" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="33" id="2gT-9Z-6F4"/>
<constraint firstItem="109" firstAttribute="top" secondItem="YkJ-lr-EP6" secondAttribute="bottom" constant="9" id="3tl-Zc-C5n"/>
<constraint firstItem="YkJ-lr-EP6" firstAttribute="top" secondItem="J0f-Aw-dxC" secondAttribute="bottom" constant="7" id="5fd-qi-hJ6"/>
<constraint firstItem="n7q-ew-DYu" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="32" id="2gT-9Z-6F4"/>
<constraint firstItem="YkJ-lr-EP6" firstAttribute="top" secondItem="J0f-Aw-dxC" secondAttribute="bottom" constant="8" id="5fd-qi-hJ6"/>
<constraint firstItem="13" firstAttribute="top" secondItem="uHU-aL-du7" secondAttribute="bottom" constant="11" id="5uq-In-4bN"/>
<constraint firstItem="HaB-rc-AcW" firstAttribute="leading" secondItem="6MM-WC-Mpd" secondAttribute="leading" id="7Fs-9l-g66"/>
<constraint firstItem="TMn-LX-3Ub" firstAttribute="top" secondItem="7z2-DD-c58" secondAttribute="bottom" constant="9" id="AXY-LV-HMX"/>
<constraint firstItem="6q5-OP-iEb" firstAttribute="top" secondItem="YkJ-lr-EP6" secondAttribute="bottom" constant="9" id="AvD-Ni-GL2"/>
<constraint firstItem="veW-XM-HGs" firstAttribute="top" secondItem="6q5-OP-iEb" secondAttribute="bottom" constant="7" id="BFC-io-3tg"/>
<constraint firstItem="7z2-DD-c58" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="34" id="BUo-Us-u2B"/>
<constraint firstItem="15" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="33" id="Be1-9m-alA"/>
<constraint firstItem="n7q-ew-DYu" firstAttribute="top" secondItem="TMn-LX-3Ub" secondAttribute="bottom" constant="7" id="BtQ-PX-1e1"/>
<constraint firstItem="n7q-ew-DYu" firstAttribute="top" secondItem="TMn-LX-3Ub" secondAttribute="bottom" constant="8" id="BtQ-PX-1e1"/>
<constraint firstItem="109" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="20" id="Cu9-uO-BZG"/>
<constraint firstItem="FCB-ax-ARZ" firstAttribute="top" secondItem="HaB-rc-AcW" secondAttribute="bottom" constant="6" id="EAW-lg-2pb"/>
<constraint firstItem="FCB-ax-ARZ" firstAttribute="leading" secondItem="HaB-rc-AcW" secondAttribute="leading" id="GjC-qu-Nxe"/>
<constraint firstItem="j8R-Hj-3dj" firstAttribute="top" secondItem="mzw-F2-aAQ" secondAttribute="bottom" constant="6" id="H9h-Dz-FOL"/>
<constraint firstItem="bE0-Lq-Pj7" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="20" id="KKo-uq-jJx"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="6q5-OP-iEb" secondAttribute="trailing" constant="20" symbolic="YES" id="L3C-R7-eDB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="7z2-DD-c58" secondAttribute="trailing" constant="20" symbolic="YES" id="O2M-YY-tO1"/>
<constraint firstItem="IpX-f7-rTL" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="20" id="P7a-oT-uxv"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="J0f-Aw-dxC" secondAttribute="trailing" constant="20" symbolic="YES" id="R6n-8o-P1a"/>
@ -574,16 +628,19 @@
<constraint firstItem="IpX-f7-rTL" firstAttribute="top" secondItem="XWo-36-xGi" secondAttribute="top" constant="20" id="YaG-ab-LJH"/>
<constraint firstItem="uHU-aL-du7" firstAttribute="top" secondItem="IpX-f7-rTL" secondAttribute="bottom" constant="8" symbolic="YES" id="aet-Zq-v6x"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="TMn-LX-3Ub" secondAttribute="trailing" constant="20" symbolic="YES" id="atz-4L-U9s"/>
<constraint firstItem="veW-XM-HGs" firstAttribute="leading" secondItem="YkJ-lr-EP6" secondAttribute="leading" id="bS2-qk-WNJ"/>
<constraint firstItem="HaB-rc-AcW" firstAttribute="top" secondItem="6MM-WC-Mpd" secondAttribute="bottom" constant="6" symbolic="YES" id="cBa-57-gWH"/>
<constraint firstItem="ETa-09-qWI" firstAttribute="top" secondItem="IpX-f7-rTL" secondAttribute="bottom" constant="11" id="cx2-US-uOU"/>
<constraint firstItem="109" firstAttribute="top" secondItem="veW-XM-HGs" secondAttribute="bottom" constant="9" id="dEy-HE-bug"/>
<constraint firstItem="6q5-OP-iEb" firstAttribute="leading" secondItem="J0f-Aw-dxC" secondAttribute="leading" id="dgw-FU-3jV"/>
<constraint firstItem="6MM-WC-Mpd" firstAttribute="top" secondItem="j8R-Hj-3dj" secondAttribute="bottom" constant="6" id="dmy-Au-peI"/>
<constraint firstItem="YkJ-lr-EP6" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="33" id="e3v-m2-co7"/>
<constraint firstItem="YkJ-lr-EP6" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="32" id="e3v-m2-co7"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="109" secondAttribute="trailing" constant="20" symbolic="YES" id="ebL-Oq-Ode"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="bE0-Lq-Pj7" secondAttribute="trailing" constant="20" symbolic="YES" id="emk-wh-n8I"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="IpX-f7-rTL" secondAttribute="trailing" constant="20" symbolic="YES" id="gjI-tV-vEA"/>
<constraint firstItem="mzw-F2-aAQ" firstAttribute="top" secondItem="bE0-Lq-Pj7" secondAttribute="bottom" constant="6" symbolic="YES" id="hiH-dI-6Ql"/>
<constraint firstItem="bE0-Lq-Pj7" firstAttribute="top" secondItem="109" secondAttribute="bottom" constant="6" symbolic="YES" id="jRO-Gt-vAh"/>
<constraint firstItem="J0f-Aw-dxC" firstAttribute="top" secondItem="n7q-ew-DYu" secondAttribute="bottom" constant="8" symbolic="YES" id="m8L-ij-hQ9"/>
<constraint firstItem="J0f-Aw-dxC" firstAttribute="top" secondItem="n7q-ew-DYu" secondAttribute="bottom" constant="7" id="m8L-ij-hQ9"/>
<constraint firstItem="6MM-WC-Mpd" firstAttribute="leading" secondItem="j8R-Hj-3dj" secondAttribute="leading" id="mbl-sV-kc8"/>
<constraint firstItem="mzw-F2-aAQ" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="20" id="pHT-DE-qdx"/>
<constraint firstItem="ETa-09-qWI" firstAttribute="leading" secondItem="XWo-36-xGi" secondAttribute="leading" constant="33" id="qFL-i6-eUT"/>
@ -595,7 +652,7 @@
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="13" secondAttribute="trailing" constant="20" symbolic="YES" id="yxg-7J-xR5"/>
<constraint firstItem="FCB-ax-ARZ" firstAttribute="trailing" secondItem="HaB-rc-AcW" secondAttribute="trailing" id="z0l-7W-j7D"/>
</constraints>
<point key="canvasLocation" x="-376" y="-618"/>
<point key="canvasLocation" x="-896.5" y="-91"/>
</visualEffectView>
<visualEffectView blendingMode="behindWindow" material="sidebar" state="followsWindowActiveState" id="Rnp-LM-RIF" userLabel="vwrDictionary">
<rect key="frame" x="0.0" y="0.0" width="445" height="250"/>
@ -613,7 +670,7 @@
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="s7t-Kk-EPu">
<rect key="frame" x="20" y="181" width="347" height="20"/>
<rect key="frame" x="20" y="181" width="347" height="21"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" allowsUndo="NO" borderStyle="border" baseWritingDirection="leftToRight" alignment="left" drawsBackground="YES" usesSingleLineMode="YES" id="REC-r4-T7m">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -708,7 +765,7 @@
</connections>
</button>
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MPN-np-SbT" userLabel="btnOpenFolderToSpecifyForUserData">
<rect key="frame" x="366" y="180" width="30" height="21"/>
<rect key="frame" x="366" y="181" width="30" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="19" id="BjU-G3-RBx"/>
<constraint firstAttribute="width" constant="30" id="T0S-6A-QBe"/>
@ -722,7 +779,7 @@
</buttonCell>
</button>
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jXe-xz-9Sd" userLabel="btnOpenFolderToSpecifyForUserData">
<rect key="frame" x="395" y="180" width="30" height="21"/>
<rect key="frame" x="395" y="181" width="30" height="19"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="k59-3x-inJ"/>
<constraint firstAttribute="height" constant="19" id="rUV-D4-dXJ"/>
@ -767,7 +824,7 @@
<constraint firstItem="B3F-E1F-360" firstAttribute="top" secondItem="D46-A7D-E0E" secondAttribute="bottom" constant="6" symbolic="YES" id="tJf-3S-RdV"/>
<constraint firstItem="s7t-Kk-EPu" firstAttribute="firstBaseline" secondItem="MPN-np-SbT" secondAttribute="baseline" id="wrr-VJ-Muv"/>
</constraints>
<point key="canvasLocation" x="-896.5" y="-214"/>
<point key="canvasLocation" x="-393" y="-383"/>
</visualEffectView>
<visualEffectView blendingMode="behindWindow" material="sidebar" state="followsWindowActiveState" id="U4q-xw-mc0" userLabel="vwrKeyboard">
<rect key="frame" x="0.0" y="0.0" width="445" height="289"/>
@ -995,7 +1052,7 @@
<constraint firstItem="MVw-Gx-ueg" firstAttribute="leading" secondItem="ZWc-3p-TII" secondAttribute="leading" id="wWr-35-Mkg"/>
<constraint firstItem="Md1-t7-hU4" firstAttribute="top" secondItem="hab-1o-1kS" secondAttribute="bottom" constant="8" symbolic="YES" id="zZC-jv-TR3"/>
</constraints>
<point key="canvasLocation" x="-377.5" y="-194.5"/>
<point key="canvasLocation" x="-392" y="-704"/>
</visualEffectView>
</objects>
</document>

View File

@ -26,6 +26,7 @@
"7.title" = "ETen";
"7fV-x8-WHQ.title" = "MiTAC";
"8.title" = "Hsu";
"8tT-hw-Hhr.title" = "Check for updates automatically";
"9.title" = "ETen26";
"92.title" = "OtherViews";
"93.title" = "12";
@ -62,20 +63,24 @@
"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1";
"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2";
"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3";
"lblUpperCaseLetterKeyBehavior.title" = "Choose the behavior of Shift+Letter key with letter inputs.";
"Parser11.title" = "Secondary Pinyin with Numeral Intonation";
"Parser12.title" = "Yale Pinyin with Numeral Intonation";
"Parser13.title" = "Hualuo Pinyin with Numeral Intonation";
"Parser14.title" = "Universal Pinyin with Numeral Intonation";
"Pg5-G9-pY5.title" = "Choose the behavior of (Shift+)Space key with candidates.";
"QUQ-oY-4Hc.label" = "General";
"rdoUpperCaseLetterKeyBehavior0.title" = "Type them into inline composition buffer";
"rdoUpperCaseLetterKeyBehavior1.title" = "Directly commit lowercased letters";
"rdoUpperCaseLetterKeyBehavior2.title" = "Directly commit uppercased letters";
"RQ6-MS-m4C.title" = "Choose your preferred keyboard layout and phonetic parser.";
"RUG-ls-KyA.title" = "Push the cursor in front of the phrase after selection";
"rVQ-Hx-cGi.title" = "Japanese";
"s7u-Fm-dVg.title" = "Cycling Pages";
"shc-Nu-UsM.title" = "Show page buttons in candidate list";
"sZx-18-8dO.title" = "Debug Mode";
"TXr-FF-ehw.title" = "Traditional Chinese";
"ueU-Rz-a1C.title" = "Choose the behavior of (Shift+)Tab key in the candidate window.";
"VdT-fw-7pQ.title" = "Debug Mode";
"vpd-zx-GIk.title" = "Seigyou (JinYei)";
"W24-T4-cg0.title" = "Enable CNS11643 Support (2022-07-20)";
"wFR-zX-M8H.title" = "Show Hanyu-Pinyin in the inline composition buffer & tooltip";
@ -96,5 +101,4 @@
"xjP-r7-GaK.title" = "Dachen 26 (libChewing)";
"XqL-rf-X6d.title" = "Space to +cycle pages, Shift+Space to +cycle candidates";
"xrE-8T-WKO.label" = "Advanced";
"Z9t-P0-BLF.title" = "Check for updates automatically";
"ZEv-Q2-mYL.title" = "Change user interface language (will reboot the IME).";

View File

@ -26,6 +26,7 @@
"7.title" = "倚天伝統配列";
"7fV-x8-WHQ.title" = "神通配列";
"8.title" = "許氏国音自然配列";
"8tT-hw-Hhr.title" = "アプリの更新通知を受く";
"9.title" = "倚天形忘れ配列 (26キー)";
"92.title" = "OtherViews";
"93.title" = "12";
@ -62,20 +63,24 @@
"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1";
"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2";
"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3";
"lblUpperCaseLetterKeyBehavior.title" = "Shift+文字キーの行為をご指定ください。";
"Parser11.title" = "国音二式 (ローマ字+数字音調)";
"Parser12.title" = "イェール弁音 (ローマ字+数字音調)";
"Parser13.title" = "中華ローマ弁音 (ローマ字+数字音調)";
"Parser14.title" = "汎用弁音 (ローマ字+数字音調)";
"Pg5-G9-pY5.title" = "入力候補についての (Shift+)Space キーの輪番切替対象をご指定ください。";
"QUQ-oY-4Hc.label" = "全般";
"rdoUpperCaseLetterKeyBehavior0.title" = "入力緩衝列にローマ字入力";
"rdoUpperCaseLetterKeyBehavior1.title" = "ローマ字(小文字)を直接出力";
"rdoUpperCaseLetterKeyBehavior2.title" = "ローマ字(大文字)を直接出力";
"RQ6-MS-m4C.title" = "お好きなるキーボードとそれに相応しい注音配列をお選びください。";
"RUG-ls-KyA.title" = "候補選択の直後、すぐカーソルを単語の向こうに推す";
"rVQ-Hx-cGi.title" = "和語";
"s7u-Fm-dVg.title" = "ページ";
"shc-Nu-UsM.title" = "ページボタンを表示";
"sZx-18-8dO.title" = "欠陥辿着モード";
"TXr-FF-ehw.title" = "繁体中国語";
"ueU-Rz-a1C.title" = "入力候補陳列での (Shift+)Tab キーの輪番切替対象をご指定ください。";
"VdT-fw-7pQ.title" = "欠陥辿着モード";
"vpd-zx-GIk.title" = "精業配列";
"W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-07-20)";
"wFR-zX-M8H.title" = "弁音合併入力(入力緩衝列とヒントで音読みを漢語弁音に)";
@ -96,5 +101,4 @@
"xjP-r7-GaK.title" = "酷音大千 26 キー配列";
"XqL-rf-X6d.title" = "Space で次のページ、Shift+Space で次の候補文字を";
"xrE-8T-WKO.label" = "詳細";
"Z9t-P0-BLF.title" = "アプリの更新通知を受く";
"ZEv-Q2-mYL.title" = "アプリ表示用言語をご指定ください、そして入力アプリは自動的に再起動。";

View File

@ -2,7 +2,7 @@
"10.title" = "汉语拼音+数字标调";
"100.title" = "64";
"101.title" = "96";
"110.title" = "敲空格键以选字";
"110.title" = "敲空格键以呼出候选字";
"12.title" = "基础键盘布局:";
"126.title" = "英数键盘布局:";
"128.title" = "OtherViews";
@ -26,6 +26,7 @@
"7.title" = "倚天传统";
"7fV-x8-WHQ.title" = "神通";
"8.title" = "许氏(国音&自然)";
"8tT-hw-Hhr.title" = "自动检查软件更新";
"9.title" = "倚天二十六键";
"92.title" = "OtherViews";
"93.title" = "12";
@ -62,20 +63,24 @@
"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1";
"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2";
"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3";
"lblUpperCaseLetterKeyBehavior.title" = "指定 Shift+字母键 的行为。";
"Parser11.title" = "国音二式+数字标调";
"Parser12.title" = "耶鲁拼音+数字标调";
"Parser13.title" = "华罗拼音+数字标调";
"Parser14.title" = "通用拼音+数字标调";
"Pg5-G9-pY5.title" = "指定 (Shift+)Space 热键对候选字词而言的轮替操作对象。";
"QUQ-oY-4Hc.label" = "一般";
"rdoUpperCaseLetterKeyBehavior0.title" = "直接键入内文组字区";
"rdoUpperCaseLetterKeyBehavior1.title" = "直接递交小写字母";
"rdoUpperCaseLetterKeyBehavior2.title" = "直接递交大写字母";
"RQ6-MS-m4C.title" = "选择您所偏好的系统键盘布局与注音分析器排列。";
"RUG-ls-KyA.title" = "在选字后将光标置于该字词的前方";
"rVQ-Hx-cGi.title" = "和语";
"s7u-Fm-dVg.title" = "轮替页面";
"shc-Nu-UsM.title" = "在选字窗内显示翻页按钮";
"sZx-18-8dO.title" = "侦错模式";
"TXr-FF-ehw.title" = "繁体中文";
"ueU-Rz-a1C.title" = "指定 (Shift+)Tab 热键在选字窗内的轮替操作对象。";
"VdT-fw-7pQ.title" = "侦错模式";
"vpd-zx-GIk.title" = "精业";
"W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-07-20)";
"wFR-zX-M8H.title" = "拼音并击(组字区与工具提示内显示汉语拼音)";
@ -96,5 +101,4 @@
"xjP-r7-GaK.title" = "酷音大千二十六键";
"XqL-rf-X6d.title" = "Space 换下一页Shift+Space 换选下一个候选字。";
"xrE-8T-WKO.label" = "进阶";
"Z9t-P0-BLF.title" = "自动检查软件更新";
"ZEv-Q2-mYL.title" = "变更使用者界面语言,会自动重新启动输入法。";

View File

@ -2,7 +2,7 @@
"10.title" = "漢語拼音+數字標調";
"100.title" = "64";
"101.title" = "96";
"110.title" = "敲空格鍵以選字";
"110.title" = "敲空格鍵以呼出候選字";
"12.title" = "注音排列:";
"126.title" = "基礎鍵盤佈局:";
"128.title" = "OtherViews";
@ -26,6 +26,7 @@
"7.title" = "倚天傳統";
"7fV-x8-WHQ.title" = "神通";
"8.title" = "許氏(國音&自然)";
"8tT-hw-Hhr.title" = "自動檢查軟體更新";
"9.title" = "倚天二十六鍵";
"92.title" = "OtherViews";
"93.title" = "12";
@ -62,20 +63,24 @@
"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1";
"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2";
"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3";
"lblUpperCaseLetterKeyBehavior.title" = "指定 Shift+字母鍵 的行為。";
"Parser11.title" = "國音二式+數字標調";
"Parser12.title" = "耶魯拼音+數字標調";
"Parser13.title" = "華羅拼音+數字標調";
"Parser14.title" = "通用拼音+數字標調";
"Pg5-G9-pY5.title" = "指定 (Shift+)Space 熱鍵對候選字詞而言的輪替操作對象。";
"QUQ-oY-4Hc.label" = "一般";
"rdoUpperCaseLetterKeyBehavior0.title" = "直接鍵入內文組字區";
"rdoUpperCaseLetterKeyBehavior1.title" = "直接遞交小寫字母";
"rdoUpperCaseLetterKeyBehavior2.title" = "直接遞交大寫字母";
"RQ6-MS-m4C.title" = "選擇您所偏好的系統鍵盤佈局與注音分析器排列。";
"RUG-ls-KyA.title" = "在選字後將游標置於該字詞的前方";
"rVQ-Hx-cGi.title" = "和語";
"s7u-Fm-dVg.title" = "輪替頁面";
"shc-Nu-UsM.title" = "在選字窗內顯示翻頁按鈕";
"sZx-18-8dO.title" = "偵錯模式";
"TXr-FF-ehw.title" = "繁體中文";
"ueU-Rz-a1C.title" = "指定 (Shift+)Tab 熱鍵在選字窗內的輪替操作對象。";
"VdT-fw-7pQ.title" = "偵錯模式";
"vpd-zx-GIk.title" = "精業";
"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-07-20)";
"wFR-zX-M8H.title" = "拼音並擊(組字區與工具提示內顯示漢語拼音)";
@ -96,5 +101,4 @@
"xjP-r7-GaK.title" = "酷音大千二十六鍵";
"XqL-rf-X6d.title" = "Space 換下一頁Shift+Space 換選下一個候選字";
"xrE-8T-WKO.label" = "進階";
"Z9t-P0-BLF.title" = "自動檢查軟體更新";
"ZEv-Q2-mYL.title" = "變更使用者介面語言,會自動重新啟動輸入法。";

View File

@ -3,9 +3,9 @@
<plist version="1.0">
<dict>
<key>CFBundleShortVersionString</key>
<string>1.8.8</string>
<string>1.9.0</string>
<key>CFBundleVersion</key>
<string>1988</string>
<string>1990</string>
<key>UpdateInfoEndpoint</key>
<string>https://gitee.com/vchewing/vChewing-macOS/raw/main/Update-Info.plist</string>
<key>UpdateInfoSite</key>

View File

@ -726,7 +726,7 @@
<key>USE_HFS+_COMPRESSION</key>
<false/>
<key>VERSION</key>
<string>1.8.8</string>
<string>1.9.0</string>
</dict>
<key>TYPE</key>
<integer>0</integer>

View File

@ -12,18 +12,17 @@
5B21176C287539BB000443A9 /* ctlInputMethod_HandleStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */; };
5B21176E28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */; };
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */; };
5B2170E0289FACAD00BE7304 /* 7_LangModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */; };
5B2170E1289FACAD00BE7304 /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */; };
5B2170E2289FACAD00BE7304 /* 8_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */; };
5B2170E3289FACAD00BE7304 /* 3_Candidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */; };
5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DB289FACAC00BE7304 /* 2_Walker.swift */; };
5B2170E5289FACAD00BE7304 /* 6_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DC289FACAC00BE7304 /* 6_Node.swift */; };
5B2170E6289FACAD00BE7304 /* 4_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DD289FACAC00BE7304 /* 4_Span.swift */; };
5B2170E7289FACAD00BE7304 /* 1_Compositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DE289FACAC00BE7304 /* 1_Compositor.swift */; };
5B2170E8289FACAD00BE7304 /* 5_Vertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2170DF289FACAC00BE7304 /* 5_Vertex.swift */; };
5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */; };
5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; };
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */; };
5B38F59B281E2E49007D5F5D /* 7_KeyValuePaired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */; };
5B38F59C281E2E49007D5F5D /* 2_Grid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */; };
5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */; };
5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */; };
5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */; };
5B38F5A1281E2E49007D5F5D /* 1_Compositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1515FC0EB100ABF4B3 /* 1_Compositor.swift */; };
5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */; };
5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */; };
5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */; };
5B3A87BC28597CDB0090E163 /* LMSymbolNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3A87BB28597CDB0090E163 /* LMSymbolNode.swift */; };
5B40730C281672610023DFFF /* lmAssociates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B407309281672610023DFFF /* lmAssociates.swift */; };
5B40730D281672610023DFFF /* lmReplacements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B40730A281672610023DFFF /* lmReplacements.swift */; };
@ -213,6 +212,15 @@
5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleStates.swift; sourceTree = "<group>"; };
5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleDisplay.swift; sourceTree = "<group>"; };
5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Delegates.swift; sourceTree = "<group>"; };
5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 7_LangModel.swift; sourceTree = "<group>"; };
5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 0_Megrez.swift; sourceTree = "<group>"; };
5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 8_Unigram.swift; sourceTree = "<group>"; };
5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 3_Candidate.swift; sourceTree = "<group>"; };
5B2170DB289FACAC00BE7304 /* 2_Walker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 2_Walker.swift; sourceTree = "<group>"; };
5B2170DC289FACAC00BE7304 /* 6_Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6_Node.swift; sourceTree = "<group>"; };
5B2170DD289FACAC00BE7304 /* 4_Span.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 4_Span.swift; sourceTree = "<group>"; };
5B2170DE289FACAC00BE7304 /* 1_Compositor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 1_Compositor.swift; sourceTree = "<group>"; };
5B2170DF289FACAC00BE7304 /* 5_Vertex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 5_Vertex.swift; sourceTree = "<group>"; };
5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlCandidateUniversal.swift; sourceTree = "<group>"; };
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@ -329,16 +337,6 @@
5BFDF48C27B51867009523B6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Main.strings"; sourceTree = "<group>"; };
6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewing.app; sourceTree = BUILT_PRODUCTS_DIR; };
6A0D4EF515FC0DA600ABF4B3 /* IME-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "IME-Info.plist"; sourceTree = "<group>"; };
6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Bigram.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1515FC0EB100ABF4B3 /* 1_Compositor.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 1_Compositor.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = 0_Megrez.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 2_Grid.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 7_KeyValuePaired.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 5_LanguageModel.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 4_Node.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_NodeAnchor.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_Span.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Unigram.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
@ -821,16 +819,15 @@
6A0D4F1315FC0EB100ABF4B3 /* Megrez */ = {
isa = PBXGroup;
children = (
6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */,
6A0D4F1515FC0EB100ABF4B3 /* 1_Compositor.swift */,
6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */,
6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */,
6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */,
6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */,
6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */,
6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */,
6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */,
6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */,
5B2170D8289FACAC00BE7304 /* 0_Megrez.swift */,
5B2170DE289FACAC00BE7304 /* 1_Compositor.swift */,
5B2170DB289FACAC00BE7304 /* 2_Walker.swift */,
5B2170DA289FACAC00BE7304 /* 3_Candidate.swift */,
5B2170DD289FACAC00BE7304 /* 4_Span.swift */,
5B2170DF289FACAC00BE7304 /* 5_Vertex.swift */,
5B2170DC289FACAC00BE7304 /* 6_Node.swift */,
5B2170D7289FACAC00BE7304 /* 7_LangModel.swift */,
5B2170D9289FACAC00BE7304 /* 8_Unigram.swift */,
);
path = Megrez;
sourceTree = "<group>";
@ -1155,8 +1152,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */,
5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */,
5B40730C281672610023DFFF /* lmAssociates.swift in Sources */,
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */,
@ -1169,22 +1164,25 @@
D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */,
5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */,
5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */,
5B2170E7289FACAD00BE7304 /* 1_Compositor.swift in Sources */,
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */,
5B21176E28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift in Sources */,
5B84579F2871AD2200C93B01 /* HotenkaChineseConverter.swift in Sources */,
5B887F302826AEA400B6651E /* lmCoreEX.swift in Sources */,
5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */,
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */,
5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */,
5B2170E5289FACAD00BE7304 /* 6_Node.swift in Sources */,
5B949BD92816DC5400D87B5D /* LineReader.swift in Sources */,
D456576E279E4F7B00DF6BC9 /* InputSignal.swift in Sources */,
5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */,
5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */,
5B2170E1289FACAD00BE7304 /* 0_Megrez.swift in Sources */,
5B3A87BC28597CDB0090E163 /* LMSymbolNode.swift in Sources */,
5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */,
5BA9FD4427FEF3C8002DE248 /* SegmentedControlStyleViewController.swift in Sources */,
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */,
5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */,
5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */,
5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */,
5BA0DF312817857D009E73BB /* lmUserOverride.swift in Sources */,
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */,
@ -1194,39 +1192,37 @@
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */,
5B54E743283A7D89001ECBDC /* lmCoreNS.swift in Sources */,
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
5B2170E2289FACAD00BE7304 /* 8_Unigram.swift in Sources */,
5B21176C287539BB000443A9 /* ctlInputMethod_HandleStates.swift in Sources */,
5B38F59B281E2E49007D5F5D /* 7_KeyValuePaired.swift in Sources */,
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */,
5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */,
5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */,
5B62A33827AE79CD00A19448 /* StringUtils.swift in Sources */,
5B2170E3289FACAD00BE7304 /* 3_Candidate.swift in Sources */,
5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */,
5B2170E6289FACAD00BE7304 /* 4_Span.swift in Sources */,
5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */,
5BA9FD3E27FEF3C8002DE248 /* Utilities.swift in Sources */,
5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */,
5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */,
5B8457A12871ADBE00C93B01 /* HotenkaCCBridge.swift in Sources */,
5B38F59C281E2E49007D5F5D /* 2_Grid.swift in Sources */,
5B40730D281672610023DFFF /* lmReplacements.swift in Sources */,
5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */,
5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */,
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
5B2170E0289FACAD00BE7304 /* 7_LangModel.swift in Sources */,
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */,
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */,
5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */,
5B2170E8289FACAD00BE7304 /* 5_Vertex.swift in Sources */,
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */,
5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */,
5B949BDB2816DDBC00D87B5D /* LMConsolidator.swift in Sources */,
5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */,
5BFDF011289635C100417BBC /* ctlCandidateIMK.swift in Sources */,
5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */,
5BA58646289BCFAC0077D02F /* ShiftKeyUpChecker.swift in Sources */,
5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */,
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
5B38F5A1281E2E49007D5F5D /* 1_Compositor.swift in Sources */,
5BE377A0288FED8D0037365B /* KeyHandler_HandleComposition.swift in Sources */,
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */,
);
@ -1413,7 +1409,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
@ -1423,7 +1419,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests;
@ -1452,13 +1448,13 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests;
@ -1489,7 +1485,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
DEAD_CODE_STRIPPING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
@ -1510,7 +1506,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
@ -1539,7 +1535,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
DEAD_CODE_STRIPPING = YES;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1556,7 +1552,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
@ -1670,7 +1666,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
@ -1698,7 +1694,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1725,7 +1721,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
@ -1747,7 +1743,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1769,7 +1765,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
@ -1789,7 +1785,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1811,7 +1807,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1988;
CURRENT_PROJECT_VERSION = 1990;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
@ -1825,7 +1821,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.8.8;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -98,16 +98,6 @@ class PrefManagerTests: XCTestCase {
XCTAssert(mgrPrefs.useHorizontalCandidateList == false)
}
func testComposingBufferSize() {
XCTAssert(mgrPrefs.composingBufferSize == 20)
mgrPrefs.composingBufferSize = 10
XCTAssert(mgrPrefs.composingBufferSize == 10)
mgrPrefs.composingBufferSize = 4
XCTAssert(mgrPrefs.composingBufferSize == 10)
mgrPrefs.composingBufferSize = 50
XCTAssert(mgrPrefs.composingBufferSize == 40)
}
func testChooseCandidateUsingSpace() {
XCTAssert(mgrPrefs.chooseCandidateUsingSpace == true)
mgrPrefs.chooseCandidateUsingSpace = false