1.8.0 // Improved handling. Merge Gitee PR!59 from upd/1.8.0

This commit is contained in:
ShikiSuen 2022-07-12 15:48:03 +00:00 committed by Gitee
commit 54a6be7257
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
42 changed files with 892 additions and 793 deletions

2
FAQ.md
View File

@ -55,7 +55,7 @@ xattr -dr com.apple.quarantine ~/Downloads/vChewingInstaller.app
### 5. 選字窗位置不對欸。 ### 5. 選字窗位置不對欸。
這往往是您在字時使用的應用程式,並沒有正確地將正確的游標位置告知 IMK、導致輸入法無法得知相關的資訊使然。您在某個應用程式中字,輸入游標的位置到底在哪裡,一開始只有那個應用程式知道,然後,那個應用程式必須把正確的位置告知輸入法,輸入法才知道應該在什麼位置,顯示像是選字窗這樣的使用者介面元件。某些應用完全沒有認真處理與 macOS 的 IMK 框架有關的內容,所以就通知了輸入法一個奇怪的位置資訊,有時候座標根本就在螢幕大小之外。威注音在使用的 Voltaire MK3 與上游使用的 Voltaire MK2 的判斷方法相同:如果某個應用程式將奇怪的位置(不在任何一個螢幕的範圍內)告知給 IMK那麼輸入法就會想辦法把選字窗擺在螢幕範圍內最接近的位置。比方說如果是 y 軸超過了螢幕尺寸,就會改在螢幕的最上方顯示。 這往往是您在字時使用的應用程式,並沒有正確地將正確的游標位置告知 IMK、導致輸入法無法得知相關的資訊使然。您在某個應用程式中字,輸入游標的位置到底在哪裡,一開始只有那個應用程式知道,然後,那個應用程式必須把正確的位置告知輸入法,輸入法才知道應該在什麼位置,顯示像是選字窗這樣的使用者介面元件。某些應用完全沒有認真處理與 macOS 的 IMK 框架有關的內容,所以就通知了輸入法一個奇怪的位置資訊,有時候座標根本就在螢幕大小之外。威注音在使用的 Voltaire MK3 與上游使用的 Voltaire MK2 的判斷方法相同:如果某個應用程式將奇怪的位置(不在任何一個螢幕的範圍內)告知給 IMK那麼輸入法就會想辦法把選字窗擺在螢幕範圍內最接近的位置。比方說如果是 y 軸超過了螢幕尺寸,就會改在螢幕的最上方顯示。
### 6. 自訂使用者語彙資料該怎麼管理? ### 6. 自訂使用者語彙資料該怎麼管理?

@ -1 +1 @@
Subproject commit 9301650c6e27b31730e4f060f94c4cab0a28579c Subproject commit 50250d970a3481ec7653a71120413708a76224d8

View File

@ -139,9 +139,9 @@ enum InputState {
/// 西 .NotEmpty /// 西 .NotEmpty
class AssociatedPhrases: InputStateProtocol { class AssociatedPhrases: InputStateProtocol {
public var type: StateType { .ofAssociatedPhrases } public var type: StateType { .ofAssociatedPhrases }
private(set) var candidates: [String] = [] private(set) var candidates: [(String, String)] = []
private(set) var isTypingVertical: Bool = false private(set) var isTypingVertical: Bool = false
init(candidates: [String], isTypingVertical: Bool) { init(candidates: [(String, String)], isTypingVertical: Bool) {
self.candidates = candidates self.candidates = candidates
self.isTypingVertical = isTypingVertical self.isTypingVertical = isTypingVertical
} }
@ -228,6 +228,27 @@ enum InputState {
return lowerBoundLiteral..<upperBoundLiteral return lowerBoundLiteral..<upperBoundLiteral
} }
var literalReadingThread: String {
var arrOutput = [String]()
for neta in readings[literalMarkedRange] {
var neta = neta
if neta.isEmpty { continue }
if neta.contains("_") {
arrOutput.append("??")
continue
}
if mgrPrefs.showHanyuPinyinInCompositionBuffer { // ->->調
neta = Tekkon.restoreToneOneInZhuyinKey(target: neta)
neta = Tekkon.cnvPhonaToHanyuPinyin(target: neta)
neta = Tekkon.cnvHanyuPinyinToTextbookStyle(target: neta)
} else {
neta = Tekkon.cnvZhuyinChainToTextbookReading(target: neta)
}
arrOutput.append(neta)
}
return arrOutput.joined(separator: " ")
}
private var deleteTargetExists = false private var deleteTargetExists = false
var tooltip: String { var tooltip: String {
if composingBuffer.count != readings.count { if composingBuffer.count != readings.count {
@ -252,15 +273,14 @@ enum InputState {
return String( return String(
format: NSLocalizedString( format: NSLocalizedString(
"\"%@\" length must ≥ 2 for a user phrase.", comment: "" "\"%@\" length must ≥ 2 for a user phrase.", comment: ""
), text ) + "\n// " + literalReadingThread, text
) )
} else if literalMarkedRange.count > allowedMarkRange.upperBound { } else if literalMarkedRange.count > allowedMarkRange.upperBound {
ctlInputMethod.tooltipController.setColor(state: .denialOverflow) ctlInputMethod.tooltipController.setColor(state: .denialOverflow)
return String( return String(
format: NSLocalizedString( format: NSLocalizedString(
"\"%@\" length should ≤ %d for a user phrase.", comment: "" "\"%@\" length should ≤ %d for a user phrase.", comment: ""
), ) + "\n// " + literalReadingThread, text, allowedMarkRange.upperBound
text, allowedMarkRange.upperBound
) )
} }
@ -275,12 +295,13 @@ enum InputState {
return String( return String(
format: NSLocalizedString( format: NSLocalizedString(
"\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude.", comment: "" "\"%@\" already exists: ENTER to boost, \n SHIFT+CMD+ENTER to exclude.", comment: ""
), text ) + "\n// " + literalReadingThread, text
) )
} }
ctlInputMethod.tooltipController.resetColor() ctlInputMethod.tooltipController.resetColor()
return String( return String(
format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: ""), format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: "") + "\n// "
+ literalReadingThread,
text text
) )
} }
@ -386,10 +407,10 @@ enum InputState {
/// .ChoosingCandidate: 使 /// .ChoosingCandidate: 使
class ChoosingCandidate: NotEmpty { class ChoosingCandidate: NotEmpty {
override public var type: StateType { .ofChooseCandidate } override public var type: StateType { .ofChooseCandidate }
private(set) var candidates: [String] private(set) var candidates: [(String, String)]
private(set) var isTypingVertical: Bool private(set) var isTypingVertical: Bool
init(composingBuffer: String, cursorIndex: Int, candidates: [String], isTypingVertical: Bool) { init(composingBuffer: String, cursorIndex: Int, candidates: [(String, String)], isTypingVertical: Bool) {
self.candidates = candidates self.candidates = candidates
self.isTypingVertical = isTypingVertical self.isTypingVertical = isTypingVertical
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
@ -411,7 +432,7 @@ enum InputState {
self.node = node self.node = node
let candidates = node.children?.map(\.title) ?? [String]() let candidates = node.children?.map(\.title) ?? [String]()
super.init( super.init(
composingBuffer: "", cursorIndex: 0, candidates: candidates, composingBuffer: "", cursorIndex: 0, candidates: candidates.map { ("", $0) },
isTypingVertical: isTypingVertical isTypingVertical: isTypingVertical
) )
} }

View File

@ -56,7 +56,7 @@ class KeyHandler {
var compositor: Megrez.Compositor // var compositor: Megrez.Compositor //
var currentLM: vChewing.LMInstantiator = .init() // var currentLM: vChewing.LMInstantiator = .init() //
var currentUOM: vChewing.LMUserOverride = .init() // var currentUOM: vChewing.LMUserOverride = .init() //
var walkedAnchors: [Megrez.NodeAnchor] = [] // var walkedAnchors: [Megrez.NodeAnchor] { compositor.walkedAnchors } //
/// (ctlInputMethod)便 /// (ctlInputMethod)便
var delegate: KeyHandlerDelegate? var delegate: KeyHandlerDelegate?
@ -95,7 +95,6 @@ class KeyHandler {
func clear() { func clear() {
composer.clear() composer.clear()
compositor.clear() compositor.clear()
walkedAnchors.removeAll()
} }
// MARK: - Functions dealing with Megrez. // MARK: - Functions dealing with Megrez.
@ -103,7 +102,7 @@ class KeyHandler {
/// Megrez 使便 /// Megrez 使便
/// ///
/// 使 Node Crossing /// 使 Node Crossing
var actualCandidateCursorIndex: Int { var actualCandidateCursor: Int {
mgrPrefs.useRearCursorMode ? min(compositorCursorIndex, compositorLength - 1) : max(compositorCursorIndex, 1) mgrPrefs.useRearCursorMode ? min(compositorCursorIndex, compositorLength - 1) : max(compositorCursorIndex, 1)
} }
@ -113,11 +112,11 @@ class KeyHandler {
/// ///
/// ///
func walk() { func walk() {
walkedAnchors = compositor.walk() compositor.walk()
// GraphViz // GraphViz
if mgrPrefs.isDebugModeEnabled { if mgrPrefs.isDebugModeEnabled {
let result = compositor.grid.dumpDOT let result = compositor.dumpDOT
do { do {
try result.write( try result.write(
toFile: "/private/var/tmp/vChewing-visualization.dot", toFile: "/private/var/tmp/vChewing-visualization.dot",
@ -137,12 +136,10 @@ class KeyHandler {
/// ///
var commitOverflownCompositionAndWalk: String { var commitOverflownCompositionAndWalk: String {
var textToCommit = "" var textToCommit = ""
if compositor.grid.width > mgrPrefs.composingBufferSize, !walkedAnchors.isEmpty { if compositor.width > mgrPrefs.composingBufferSize, !walkedAnchors.isEmpty {
let anchor: Megrez.NodeAnchor = walkedAnchors[0] let anchor: Megrez.NodeAnchor = walkedAnchors[0]
if let theNode = anchor.node { textToCommit = anchor.node.currentPair.value
textToCommit = theNode.currentKeyValue.value compositor.removeHeadReadings(count: anchor.spanLength)
}
compositor.removeHeadReadings(count: anchor.spanningLength)
} }
walk() walk()
return textToCommit return textToCommit
@ -152,10 +149,10 @@ class KeyHandler {
/// - Parameter key: /// - Parameter key:
/// - Returns: /// - Returns:
/// nil /// nil
func buildAssociatePhraseArray(withKey key: String) -> [String] { func buildAssociatePhraseArray(withKey key: String) -> [(String, String)] {
var arrResult: [String] = [] var arrResult: [(String, String)] = []
if currentLM.hasAssociatedPhrasesFor(key: key) { if currentLM.hasAssociatedPhrasesFor(key: key) {
arrResult.append(contentsOf: currentLM.associatedPhrasesFor(key: key)) arrResult = currentLM.associatedPhrasesFor(key: key).map { ("", $0) }
} }
return arrResult return arrResult
} }
@ -165,35 +162,32 @@ class KeyHandler {
/// - Parameters: /// - Parameters:
/// - value: /// - value:
/// - respectCursorPushing: true /// - respectCursorPushing: true
func fixNode(value: String, respectCursorPushing: Bool = true) { func fixNode(candidate: (String, String), respectCursorPushing: Bool = true) {
let cursorIndex = min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength) let theCandidate: Megrez.KeyValuePaired = .init(key: candidate.0, value: candidate.1)
let adjustedCursor = max(0, min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength))
// //
let selectedNode: Megrez.NodeAnchor = compositor.grid.fixNodeSelectedCandidate( let selectedNode: Megrez.NodeAnchor = compositor.fixNodeWithCandidate(theCandidate, at: adjustedCursor)
location: cursorIndex, value: value
)
// //
if !mgrPrefs.useSCPCTypingMode { if !mgrPrefs.useSCPCTypingMode {
var addToUserOverrideModel = true var addToUserOverrideModel = true
// //
if selectedNode.spanningLength != value.count { if selectedNode.spanLength != theCandidate.value.count {
IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.") IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
addToUserOverrideModel = false addToUserOverrideModel = false
} }
if addToUserOverrideModel { if addToUserOverrideModel {
if let theNode = selectedNode.node { // SymbolLM Score -12
// SymbolLM Score -12 if selectedNode.node.scoreForPaired(candidate: theCandidate) <= -12 {
if theNode.scoreFor(candidate: value) <= -12 { IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
IME.prtDebugIntel("UOM: Score <= -12, dismissing.") addToUserOverrideModel = false
addToUserOverrideModel = false
}
} }
} }
if addToUserOverrideModel { if addToUserOverrideModel {
IME.prtDebugIntel("UOM: Start Observation.") IME.prtDebugIntel("UOM: Start Observation.")
// trigram //
// trigram //
currentUOM.observe( currentUOM.observe(
walkedAnchors: walkedAnchors, cursorIndex: cursorIndex, candidate: value, walkedAnchors: walkedAnchors, cursorIndex: adjustedCursor, candidate: theCandidate.value,
timestamp: NSDate().timeIntervalSince1970 timestamp: NSDate().timeIntervalSince1970
) )
} }
@ -204,70 +198,57 @@ class KeyHandler {
/// ///
if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing { if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing {
var nextPosition = 0 compositor.jumpCursorBySpan(to: .front)
for theAnchor in walkedAnchors {
if nextPosition >= cursorIndex { break }
nextPosition += theAnchor.spanningLength
}
if nextPosition <= compositorLength {
compositorCursorIndex = nextPosition
}
} }
} }
/// ///
func markNodesFixedIfNecessary() { func markNodesFixedIfNecessary() {
let width = compositor.grid.width let width = compositor.width
if width <= kMaxComposingBufferNeedsToWalkSize { if width <= kMaxComposingBufferNeedsToWalkSize {
return return
} }
var index = 0 var index = 0
for anchor in walkedAnchors { for anchor in walkedAnchors {
guard let node = anchor.node else { break }
if index >= width - kMaxComposingBufferNeedsToWalkSize { break } if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
if node.score < node.kSelectedCandidateScore { if anchor.node.score < Megrez.Node.kSelectedCandidateScore {
compositor.grid.fixNodeSelectedCandidate( compositor.fixNodeWithCandidate(anchor.node.currentPair, at: index + anchor.spanLength)
location: index + anchor.spanningLength, value: node.currentKeyValue.value
)
} }
index += anchor.spanningLength index += anchor.spanLength
} }
} }
/// ///
func getCandidatesArray(fixOrder: Bool = true) -> [String] { func getCandidatesArray(fixOrder: Bool = true) -> [(String, String)] {
var arrAnchors: [Megrez.NodeAnchor] = rawAnchorsOfNodes var arrAnchors: [Megrez.NodeAnchor] = rawAnchorsOfNodes
var arrCandidates: [String] = [] var arrCandidates: [Megrez.KeyValuePaired] = .init()
/// nodes /// nodes
/// ///
/// ///
if arrAnchors.isEmpty { return arrCandidates } if arrAnchors.isEmpty { return .init() }
// //
arrAnchors = arrAnchors.stableSort { $0.keyLength > $1.keyLength } arrAnchors = arrAnchors.stableSort { $0.keyLength > $1.keyLength }
// //
for currentNodeAnchor in arrAnchors { for currentCandidate in arrAnchors.map(\.node.candidates).joined() {
guard let currentNode = currentNodeAnchor.node else { continue } // / JIS
for currentCandidate in currentNode.candidates { //
// / JIS //
// arrCandidates.append(.init(key: currentCandidate.key, value: currentCandidate.value))
//
arrCandidates.append(currentCandidate.value)
}
} }
// 調 // 調
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel || mgrPrefs.useSCPCTypingMode || fixOrder { if !mgrPrefs.fetchSuggestionsFromUserOverrideModel || mgrPrefs.useSCPCTypingMode || fixOrder {
return arrCandidates return arrCandidates.map { ($0.key, $0.value) }
} }
let arrSuggestedUnigrams: [Megrez.Unigram] = fetchSuggestedCandidates().stableSort { $0.score > $1.score } let arrSuggestedUnigrams: [Megrez.Unigram] = fetchSuggestedCandidates().stableSort { $0.score > $1.score }
let arrSuggestedCandidates: [String] = arrSuggestedUnigrams.map(\.keyValue.value) let arrSuggestedCandidates: [Megrez.KeyValuePaired] = arrSuggestedUnigrams.map(\.keyValue)
arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates
arrCandidates = arrCandidates.deduplicate arrCandidates = arrCandidates.deduplicate
arrCandidates = arrCandidates.stableSort { $0.count > $1.count } arrCandidates = arrCandidates.stableSort { $0.key.split(separator: "-").count > $1.key.split(separator: "-").count }
return arrCandidates return arrCandidates.map { ($0.key, $0.value) }
} }
/// ///
@ -284,17 +265,17 @@ class KeyHandler {
if mgrPrefs.useSCPCTypingMode { return } if mgrPrefs.useSCPCTypingMode { return }
/// ///
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel { return } if !mgrPrefs.fetchSuggestionsFromUserOverrideModel { return }
/// trigram ///
let overrideValue = fetchSuggestedCandidates().first?.keyValue.value ?? "" let overrideValue = fetchSuggestedCandidates().first?.keyValue.value ?? ""
/// ///
if !overrideValue.isEmpty { if !overrideValue.isEmpty {
IME.prtDebugIntel( IME.prtDebugIntel(
"UOM: Suggestion retrieved, overriding the node score of the selected candidate.") "UOM: Suggestion retrieved, overriding the node score of the selected candidate.")
compositor.grid.overrideNodeScoreForSelectedCandidate( compositor.overrideNodeScoreForSelectedCandidate(
location: min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength), location: min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength),
value: overrideValue, value: overrideValue,
overridingScore: findHighestScore(nodes: rawAnchorsOfNodes, epsilon: kEpsilon) overridingScore: findHighestScore(nodeAnchors: rawAnchorsOfNodes, epsilon: kEpsilon)
) )
} else { } else {
IME.prtDebugIntel("UOM: Blank suggestion retrieved, dismissing.") IME.prtDebugIntel("UOM: Blank suggestion retrieved, dismissing.")
@ -306,14 +287,8 @@ class KeyHandler {
/// - nodes: /// - nodes:
/// - epsilon: /// - epsilon:
/// - Returns: /// - Returns:
func findHighestScore(nodes: [Megrez.NodeAnchor], epsilon: Double) -> Double { func findHighestScore(nodeAnchors: [Megrez.NodeAnchor], epsilon: Double) -> Double {
var highestScore: Double = 0 return nodeAnchors.map(\.node.highestUnigramScore).max() ?? 0 + epsilon
for currentAnchor in nodes {
if let theNode = currentAnchor.node {
highestScore = max(theNode.highestUnigramScore, highestScore)
}
}
return highestScore + epsilon
} }
// MARK: - Extracted methods and functions (Tekkon). // MARK: - Extracted methods and functions (Tekkon).
@ -359,41 +334,6 @@ class KeyHandler {
composer.clear() composer.clear()
} }
/// Ruby
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
func cnvZhuyinKeyToTextbookReading(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = []
for neta in target.split(separator: "-") {
var newString = String(neta)
if String(neta.reversed()[0]) == "˙" {
newString = String(neta.dropLast())
newString.insert("˙", at: newString.startIndex)
}
arrReturn.append(newString)
}
return arrReturn.joined(separator: newSeparator)
}
/// Ruby
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
func restoreToneOneInZhuyinKey(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = []
for neta in target.split(separator: "-") {
var newNeta = String(neta)
if !"ˊˇˋ˙".contains(String(neta.reversed()[0])), !neta.contains("_") {
newNeta += "1"
}
arrReturn.append(newNeta)
}
return arrReturn.joined(separator: newSeparator)
}
// MARK: - Extracted methods and functions (Megrez). // MARK: - Extracted methods and functions (Megrez).
/// ///
@ -404,8 +344,8 @@ class KeyHandler {
/// 使 nodesCrossing macOS /// 使 nodesCrossing macOS
/// nodeCrossing /// nodeCrossing
mgrPrefs.useRearCursorMode mgrPrefs.useRearCursorMode
? compositor.grid.nodesBeginningAt(location: actualCandidateCursorIndex) ? compositor.nodesBeginningAt(location: actualCandidateCursor)
: compositor.grid.nodesEndingAt(location: actualCandidateCursorIndex) : compositor.nodesEndingAt(location: actualCandidateCursor)
} }
/// ///
@ -431,13 +371,13 @@ class KeyHandler {
/// ///
func insertToCompositorAtCursor(reading: String) { func insertToCompositorAtCursor(reading: String) {
compositor.insertReadingAtCursor(reading: reading) compositor.insertReading(reading)
} }
/// ///
var compositorCursorIndex: Int { var compositorCursorIndex: Int {
get { compositor.cursorIndex } get { compositor.cursor }
set { compositor.cursorIndex = newValue } set { compositor.cursor = newValue }
} }
/// ///
@ -449,13 +389,43 @@ class KeyHandler {
/// ///
/// Rear /// Rear
func deleteCompositorReadingAtTheRearOfCursor() { func deleteCompositorReadingAtTheRearOfCursor() {
compositor.deleteReadingAtTheRearOfCursor() compositor.dropReading(direction: .rear)
} }
/// ///
/// ///
/// Front /// Front
func deleteCompositorReadingToTheFrontOfCursor() { func deleteCompositorReadingToTheFrontOfCursor() {
compositor.deleteReadingToTheFrontOfCursor() compositor.dropReading(direction: .front)
}
///
/// - Returns:
var keyLengthAtCurrentIndex: Int {
walkedAnchors[compositorCursorIndex].node.key.split(separator: "-").count
}
var nextPhrasePosition: Int {
var nextPosition = 0
for theAnchor in walkedAnchors {
if nextPosition > actualCandidateCursor { break }
nextPosition += theAnchor.spanLength
}
return min(nextPosition, compositorLength)
}
///
/// - Parameter input:
/// - Returns:
func generatePunctuationNamePrefix(withKeyCondition input: InputSignal) -> String {
if mgrPrefs.halfWidthPunctuationEnabled {
return "_half_punctuation_"
}
switch (input.isControlHold, input.isOptionHold) {
case (true, true): return "_alt_ctrl_punctuation_"
case (true, false): return "_ctrl_punctuation_"
case (false, true): return "_alt_punctuation_"
case (false, false): return "_punctuation_"
}
} }
} }

View File

@ -154,18 +154,14 @@ extension KeyHandler {
if input.isLeft { if input.isLeft {
switch ctlCandidateCurrent.currentLayout { switch ctlCandidateCurrent.currentLayout {
case .horizontal: case .horizontal:
do { if !ctlCandidateCurrent.highlightPreviousCandidate() {
if !ctlCandidateCurrent.highlightPreviousCandidate() { IME.prtDebugIntel("1145148D")
IME.prtDebugIntel("1145148D") errorCallback()
errorCallback()
}
} }
case .vertical: case .vertical:
do { if !ctlCandidateCurrent.showPreviousPage() {
if !ctlCandidateCurrent.showPreviousPage() { IME.prtDebugIntel("1919810D")
IME.prtDebugIntel("1919810D") errorCallback()
errorCallback()
}
} }
} }
return true return true
@ -187,18 +183,14 @@ extension KeyHandler {
if input.isRight { if input.isRight {
switch ctlCandidateCurrent.currentLayout { switch ctlCandidateCurrent.currentLayout {
case .horizontal: case .horizontal:
do { if !ctlCandidateCurrent.highlightNextCandidate() {
if !ctlCandidateCurrent.highlightNextCandidate() { IME.prtDebugIntel("9B65138D")
IME.prtDebugIntel("9B65138D") errorCallback()
errorCallback()
}
} }
case .vertical: case .vertical:
do { if !ctlCandidateCurrent.showNextPage() {
if !ctlCandidateCurrent.showNextPage() { IME.prtDebugIntel("9244908D")
IME.prtDebugIntel("9244908D") errorCallback()
errorCallback()
}
} }
} }
return true return true
@ -220,18 +212,14 @@ extension KeyHandler {
if input.isUp { if input.isUp {
switch ctlCandidateCurrent.currentLayout { switch ctlCandidateCurrent.currentLayout {
case .horizontal: case .horizontal:
do { if !ctlCandidateCurrent.showPreviousPage() {
if !ctlCandidateCurrent.showPreviousPage() { IME.prtDebugIntel("9B614524")
IME.prtDebugIntel("9B614524") errorCallback()
errorCallback()
}
} }
case .vertical: case .vertical:
do { if !ctlCandidateCurrent.highlightPreviousCandidate() {
if !ctlCandidateCurrent.highlightPreviousCandidate() { IME.prtDebugIntel("ASD9908D")
IME.prtDebugIntel("ASD9908D") errorCallback()
errorCallback()
}
} }
} }
return true return true
@ -242,18 +230,14 @@ extension KeyHandler {
if input.isDown { if input.isDown {
switch ctlCandidateCurrent.currentLayout { switch ctlCandidateCurrent.currentLayout {
case .horizontal: case .horizontal:
do { if !ctlCandidateCurrent.showNextPage() {
if !ctlCandidateCurrent.showNextPage() { IME.prtDebugIntel("92B990DD")
IME.prtDebugIntel("92B990DD") errorCallback()
errorCallback()
}
} }
case .vertical: case .vertical:
do { if !ctlCandidateCurrent.highlightNextCandidate() {
if !ctlCandidateCurrent.highlightNextCandidate() { IME.prtDebugIntel("6B99908D")
IME.prtDebugIntel("6B99908D") errorCallback()
errorCallback()
}
} }
} }
return true return true
@ -274,7 +258,7 @@ extension KeyHandler {
// MARK: End Key // MARK: End Key
var candidates: [String]! var candidates: [(String, String)]!
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
candidates = state.candidates candidates = state.candidates
@ -334,22 +318,8 @@ extension KeyHandler {
/// - / /// - /
/// - /// -
var punctuationNamePrefix = "" let punctuationNamePrefix: String = generatePunctuationNamePrefix(withKeyCondition: input)
if input.isOptionHold && !input.isControlHold {
punctuationNamePrefix = "_alt_punctuation_"
} else if input.isControlHold && !input.isOptionHold {
punctuationNamePrefix = "_ctrl_punctuation_"
} else if input.isControlHold && input.isOptionHold {
punctuationNamePrefix = "_alt_ctrl_punctuation_"
} else if mgrPrefs.halfWidthPunctuationEnabled {
punctuationNamePrefix = "_half_punctuation_"
} else {
punctuationNamePrefix = "_punctuation_"
}
let parser = currentMandarinParser let parser = currentMandarinParser
let arrCustomPunctuations: [String] = [ let arrCustomPunctuations: [String] = [
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),
] ]

View File

@ -225,7 +225,7 @@ extension KeyHandler {
) )
if choosingCandidates.candidates.count == 1 { if choosingCandidates.candidates.count == 1 {
clear() clear()
let text: String = choosingCandidates.candidates.first ?? "" let text: String = choosingCandidates.candidates.first?.1 ?? ""
stateCallback(InputState.Committing(textToCommit: text)) stateCallback(InputState.Committing(textToCommit: text))
if !mgrPrefs.associatedPhrasesEnabled { if !mgrPrefs.associatedPhrasesEnabled {
@ -414,20 +414,7 @@ extension KeyHandler {
/// - / /// - /
/// - /// -
var punctuationNamePrefix = "" let punctuationNamePrefix: String = generatePunctuationNamePrefix(withKeyCondition: input)
if input.isOptionHold && !input.isControlHold {
punctuationNamePrefix = "_alt_punctuation_"
} else if input.isControlHold && !input.isOptionHold {
punctuationNamePrefix = "_ctrl_punctuation_"
} else if input.isControlHold && input.isOptionHold {
punctuationNamePrefix = "_alt_ctrl_punctuation_"
} else if mgrPrefs.halfWidthPunctuationEnabled {
punctuationNamePrefix = "_half_punctuation_"
} else {
punctuationNamePrefix = "_punctuation_"
}
let parser = currentMandarinParser let parser = currentMandarinParser
let arrCustomPunctuations: [String] = [ let arrCustomPunctuations: [String] = [
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),

View File

@ -45,15 +45,15 @@ extension KeyHandler {
/// Swift.utf16NSString.length() /// Swift.utf16NSString.length()
/// ///
for theAnchor in walkedAnchors { for theAnchor in walkedAnchors {
guard let theNode = theAnchor.node else { continue } let theNode = theAnchor.node
let strNodeValue = theNode.currentKeyValue.value let strNodeValue = theNode.currentPair.value
composingBuffer += strNodeValue composingBuffer += strNodeValue
let arrSplit: [String] = Array(strNodeValue).map { String($0) } let arrSplit: [String] = Array(strNodeValue).map { String($0) }
let codepointCount = arrSplit.count let codepointCount = arrSplit.count
/// ///
/// NodeAnchorspanningLength /// NodeAnchorspanningLength
/// ///
let spanningLength: Int = theAnchor.spanningLength let spanningLength: Int = theAnchor.spanLength
if readingCursorIndex + spanningLength <= compositorCursorIndex { if readingCursorIndex + spanningLength <= compositorCursorIndex {
composedStringCursorIndex += strNodeValue.utf16.count composedStringCursorIndex += strNodeValue.utf16.count
readingCursorIndex += spanningLength readingCursorIndex += spanningLength
@ -81,10 +81,20 @@ extension KeyHandler {
case 0: case 0:
tooltipParameterRef[1] = compositor.readings[compositorCursorIndex] tooltipParameterRef[1] = compositor.readings[compositorCursorIndex]
default: default:
do { tooltipParameterRef[0] = compositor.readings[compositorCursorIndex - 1]
tooltipParameterRef[0] = compositor.readings[compositorCursorIndex - 1] tooltipParameterRef[1] = compositor.readings[compositorCursorIndex]
tooltipParameterRef[1] = compositor.readings[compositorCursorIndex] }
} ///
for (i, _) in tooltipParameterRef.enumerated() {
if tooltipParameterRef[i].isEmpty { continue }
if tooltipParameterRef[i].contains("_") { continue }
if mgrPrefs.showHanyuPinyinInCompositionBuffer { // ->->調
tooltipParameterRef[i] = Tekkon.restoreToneOneInZhuyinKey(target: tooltipParameterRef[i])
tooltipParameterRef[i] = Tekkon.cnvPhonaToHanyuPinyin(target: tooltipParameterRef[i])
tooltipParameterRef[i] = Tekkon.cnvHanyuPinyinToTextbookStyle(target: tooltipParameterRef[i])
} else {
tooltipParameterRef[i] = Tekkon.cnvZhuyinChainToTextbookReading(target: tooltipParameterRef[i])
}
} }
} }
} }
@ -318,8 +328,8 @@ extension KeyHandler {
) )
if candidateState.candidates.count == 1 { if candidateState.candidates.count == 1 {
clear() clear()
if let strtextToCommit: String = candidateState.candidates.first { if let candidateToCommit: (String, String) = candidateState.candidates.first {
stateCallback(InputState.Committing(textToCommit: strtextToCommit)) stateCallback(InputState.Committing(textToCommit: candidateToCommit.1))
stateCallback(InputState.Empty()) stateCallback(InputState.Empty())
} else { } else {
stateCallback(candidateState) stateCallback(candidateState)
@ -364,7 +374,7 @@ extension KeyHandler {
var composingBuffer = currentReadings.joined(separator: "-") var composingBuffer = currentReadings.joined(separator: "-")
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin { if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
composingBuffer = restoreToneOneInZhuyinKey(target: composingBuffer) // composingBuffer = Tekkon.restoreToneOneInZhuyinKey(target: composingBuffer) //
composingBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: composingBuffer) // composingBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: composingBuffer) //
} }
@ -394,22 +404,20 @@ extension KeyHandler {
var composed = "" var composed = ""
for theAnchor in walkedAnchors { for node in walkedAnchors.map(\.node) {
if let node = theAnchor.node { var key = node.key
var key = node.key if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin { key = Tekkon.restoreToneOneInZhuyinKey(target: key) //
key = restoreToneOneInZhuyinKey(target: key) // key = Tekkon.cnvPhonaToHanyuPinyin(target: key) //
key = Tekkon.cnvPhonaToHanyuPinyin(target: key) // key = Tekkon.cnvHanyuPinyinToTextbookStyle(target: key) // 調
key = Tekkon.cnvHanyuPinyinToTextbookStyle(target: key) // 調 key = key.replacingOccurrences(of: "-", with: " ")
key = key.replacingOccurrences(of: "-", with: " ") } else {
} else { key = Tekkon.cnvZhuyinChainToTextbookReading(target: key, newSeparator: " ")
key = cnvZhuyinKeyToTextbookReading(target: key, newSeparator: " ")
}
let value = node.currentKeyValue.value
//
composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>"
} }
let value = node.currentPair.value
//
composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>"
} }
clear() clear()
@ -651,6 +659,18 @@ extension KeyHandler {
errorCallback() errorCallback()
stateCallback(state) stateCallback(state)
} }
} else if input.isOptionHold {
if input.isControlHold {
return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
}
//
if !compositor.jumpCursorBySpan(to: .front) {
IME.prtDebugIntel("33C3B580")
errorCallback()
stateCallback(state)
return true
}
stateCallback(buildInputtingState)
} else { } else {
if compositorCursorIndex < compositorLength { if compositorCursorIndex < compositorLength {
compositorCursorIndex += 1 compositorCursorIndex += 1
@ -707,6 +727,18 @@ extension KeyHandler {
errorCallback() errorCallback()
stateCallback(state) stateCallback(state)
} }
} else if input.isOptionHold {
if input.isControlHold {
return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
}
//
if !compositor.jumpCursorBySpan(to: .rear) {
IME.prtDebugIntel("8D50DD9E")
errorCallback()
stateCallback(state)
return true
}
stateCallback(buildInputtingState)
} else { } else {
if compositorCursorIndex > 0 { if compositorCursorIndex > 0 {
compositorCursorIndex -= 1 compositorCursorIndex -= 1
@ -763,26 +795,21 @@ extension KeyHandler {
var length = 0 var length = 0
var currentAnchor = Megrez.NodeAnchor() var currentAnchor = Megrez.NodeAnchor()
let cursorIndex = min( let cursorIndex = min(
actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength
) )
for anchor in walkedAnchors { for anchor in walkedAnchors {
length += anchor.spanningLength length += anchor.spanLength
if length >= cursorIndex { if length >= cursorIndex {
currentAnchor = anchor currentAnchor = anchor
break break
} }
} }
guard let currentNode = currentAnchor.node else { let currentNode = currentAnchor.node
IME.prtDebugIntel("4F2DEC2F") let currentPaired: Megrez.KeyValuePaired = currentNode.currentPair
errorCallback()
return true
}
let currentValue = currentNode.currentKeyValue.value
var currentIndex = 0 var currentIndex = 0
if currentNode.score < currentNode.kSelectedCandidateScore { if currentNode.score < Megrez.Node.kSelectedCandidateScore {
/// 使 /// 使
/// 使 /// 使
/// 2 使 /// 2 使
@ -791,14 +818,14 @@ extension KeyHandler {
/// 使 /// 使
/// (Shift+)Tab () /// (Shift+)Tab ()
/// Shift(+CMD)+Space Tab /// Shift(+CMD)+Space Tab
if candidates[0] == currentValue { if candidates[0].0 == currentPaired.key, candidates[0].1 == currentPaired.value {
/// ///
/// ///
currentIndex = reverseModifier ? candidates.count - 1 : 1 currentIndex = reverseModifier ? candidates.count - 1 : 1
} }
} else { } else {
for candidate in candidates { for candidate in candidates {
if candidate == currentValue { if candidate.0 == currentPaired.key, candidate.1 == currentPaired.value {
if reverseModifier { if reverseModifier {
if currentIndex == 0 { if currentIndex == 0 {
currentIndex = candidates.count - 1 currentIndex = candidates.count - 1
@ -818,7 +845,7 @@ extension KeyHandler {
currentIndex = 0 currentIndex = 0
} }
fixNode(value: candidates[currentIndex], respectCursorPushing: false) fixNode(candidate: candidates[currentIndex], respectCursorPushing: false)
stateCallback(buildInputtingState) stateCallback(buildInputtingState)
return true return true

View File

@ -236,18 +236,11 @@ public struct Tekkon {
public func getComposition(isHanyuPinyin: Bool = false, isTextBookStyle: Bool = false) -> String { public func getComposition(isHanyuPinyin: Bool = false, isTextBookStyle: Bool = false) -> String {
switch isHanyuPinyin { switch isHanyuPinyin {
case false: // case false: //
var valReturnZhuyin = value.replacingOccurrences(of: " ", with: "") let valReturnZhuyin = value.replacingOccurrences(of: " ", with: "")
if isTextBookStyle, valReturnZhuyin.contains("˙") { return isTextBookStyle ? cnvZhuyinChainToTextbookReading(target: valReturnZhuyin) : valReturnZhuyin
valReturnZhuyin = String(valReturnZhuyin.dropLast())
valReturnZhuyin.insert("˙", at: valReturnZhuyin.startIndex)
}
return valReturnZhuyin
case true: // case true: //
var valReturnPinyin = Tekkon.cnvPhonaToHanyuPinyin(target: value) let valReturnPinyin = Tekkon.cnvPhonaToHanyuPinyin(target: value)
if isTextBookStyle { return isTextBookStyle ? Tekkon.cnvHanyuPinyinToTextbookStyle(target: valReturnPinyin) : valReturnPinyin
valReturnPinyin = Tekkon.cnvHanyuPinyinToTextbookStyle(target: valReturnPinyin)
}
return valReturnPinyin
} }
} }
@ -852,6 +845,41 @@ public struct Tekkon {
return targetConverted return targetConverted
} }
///
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
static func cnvZhuyinChainToTextbookReading(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = []
for neta in target.split(separator: "-") {
var newString = String(neta)
if String(neta.reversed()[0]) == "˙" {
newString = String(neta.dropLast())
newString.insert("˙", at: newString.startIndex)
}
arrReturn.append(newString)
}
return arrReturn.joined(separator: newSeparator)
}
/// 調1
/// - Parameters:
/// - target:
/// - newSeparator:
/// - Returns:
static func restoreToneOneInZhuyinKey(target: String, newSeparator: String = "-") -> String {
var arrReturn: [String] = []
for neta in target.split(separator: "-") {
var newNeta = String(neta)
if !"ˊˇˋ˙".contains(String(neta.reversed()[0])), !neta.contains("_") {
newNeta += "1"
}
arrReturn.append(newNeta)
}
return arrReturn.joined(separator: newSeparator)
}
/// 調 /// 調
static let arrPhonaToHanyuPinyin = [ // static let arrPhonaToHanyuPinyin = [ //
[" ", "1"], ["ˊ", "2"], ["ˇ", "3"], ["ˋ", "4"], ["˙", "5"], [" ", "1"], ["ˊ", "2"], ["ˇ", "3"], ["ˋ", "4"], ["˙", "5"],

View File

@ -81,7 +81,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
} }
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int) func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
-> String -> (String, String)
{ {
_ = controller // _ = controller //
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
@ -89,7 +89,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
} else if let state = state as? InputState.AssociatedPhrases { } else if let state = state as? InputState.AssociatedPhrases {
return state.candidates[index] return state.candidates[index]
} }
return "" return ("", "")
} }
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int) { func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int) {
@ -112,7 +112,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
let selectedValue = state.candidates[index] let selectedValue = state.candidates[index]
keyHandler.fixNode(value: selectedValue, respectCursorPushing: true) keyHandler.fixNode(candidate: selectedValue, respectCursorPushing: true)
let inputting = keyHandler.buildInputtingState let inputting = keyHandler.buildInputtingState
@ -137,10 +137,10 @@ extension ctlInputMethod: ctlCandidateDelegate {
if let state = state as? InputState.AssociatedPhrases { if let state = state as? InputState.AssociatedPhrases {
let selectedValue = state.candidates[index] let selectedValue = state.candidates[index]
handle(state: InputState.Committing(textToCommit: selectedValue)) handle(state: InputState.Committing(textToCommit: selectedValue.1))
if mgrPrefs.associatedPhrasesEnabled, if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState( let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: selectedValue, isTypingVertical: state.isTypingVertical withKey: selectedValue.1, isTypingVertical: state.isTypingVertical
), !associatePhrases.candidates.isEmpty ), !associatePhrases.candidates.isEmpty
{ {
handle(state: associatePhrases) handle(state: associatePhrases)

View File

@ -55,7 +55,7 @@ extension ctlInputMethod {
return false return false
} }
var isCandidateWindowVertical: Bool { var isCandidateWindowVertical: Bool {
var candidates: [String] = [] var candidates: [(String, String)] = .init()
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
candidates = state.candidates candidates = state.candidates
} else if let state = state as? InputState.AssociatedPhrases { } else if let state = state as? InputState.AssociatedPhrases {
@ -63,13 +63,11 @@ extension ctlInputMethod {
} }
if isTypingVertical { return true } if isTypingVertical { return true }
// 使 // 使
candidates.sort { //
$0.count > $1.count
}
// 使 // 使
// Beer emoji // Beer emoji
let maxCandidatesPerPage = mgrPrefs.candidateKeys.count let maxCandidatesPerPage = mgrPrefs.candidateKeys.count
let firstPageCandidates = candidates[0..<min(maxCandidatesPerPage, candidates.count)] let firstPageCandidates = candidates[0..<min(maxCandidatesPerPage, candidates.count)].map(\.1)
return firstPageCandidates.joined().count > Int(round(Double(maxCandidatesPerPage) * 1.8)) return firstPageCandidates.joined().count > Int(round(Double(maxCandidatesPerPage) * 1.8))
// true // true
} }

View File

@ -143,6 +143,10 @@ extension ctlInputMethod {
withTitle: NSLocalizedString("Optimize Memorized Phrases", comment: ""), withTitle: NSLocalizedString("Optimize Memorized Phrases", comment: ""),
action: #selector(removeUnigramsFromUOM(_:)), keyEquivalent: "" action: #selector(removeUnigramsFromUOM(_:)), keyEquivalent: ""
) )
menu.addItem(
withTitle: NSLocalizedString("Clear Memorized Phrases", comment: ""),
action: #selector(clearUOM(_:)), keyEquivalent: ""
)
menu.addItem(NSMenuItem.separator()) // --------------------- menu.addItem(NSMenuItem.separator()) // ---------------------
@ -362,6 +366,13 @@ extension ctlInputMethod {
} }
} }
@objc func clearUOM(_: Any?) {
mgrLangModel.clearUserOverrideModelData(IME.getInputMode())
if NSEvent.modifierFlags.contains(.option) {
mgrLangModel.clearUserOverrideModelData(IME.getInputMode(isReversed: true))
}
}
@objc func showAbout(_: Any?) { @objc func showAbout(_: Any?) {
(NSApp.delegate as? AppDelegate)?.showAbout() (NSApp.delegate as? AppDelegate)?.showAbout()
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)

View File

@ -28,7 +28,7 @@ import Foundation
extension vChewing { extension vChewing {
/// LMInstantiatorLMI /// LMInstantiatorLMI
/// LanguageModelProtocol 使 /// LangModelProtocol 使
/// ///
/// ///
/// LMI 調 /// LMI 調
@ -44,7 +44,7 @@ extension vChewing {
/// ///
/// LMI LMI /// LMI LMI
/// ///
public class LMInstantiator: LanguageModelProtocol { public class LMInstantiator: LangModelProtocol {
// //
public var isPhraseReplacementEnabled = false public var isPhraseReplacementEnabled = false
public var isCNSEnabled = false public var isCNSEnabled = false
@ -256,7 +256,7 @@ extension vChewing {
lmAssociates.hasValuesFor(key: key) lmAssociates.hasValuesFor(key: key)
} }
/// 滿 LanguageModelProtocol /// 滿 LangModelProtocol
public func bigramsForKeys(precedingKey _: String, key _: String) -> [Megrez.Bigram] { .init() } public func bigramsForKeys(precedingKey _: String, key _: String) -> [Megrez.Bigram] { .init() }
// MARK: - // MARK: -

View File

@ -130,13 +130,12 @@ extension vChewing {
func convertKeyFrom( func convertKeyFrom(
walkedAnchors: [Megrez.NodeAnchor], cursorIndex: Int, readingOnly: Bool = false walkedAnchors: [Megrez.NodeAnchor], cursorIndex: Int, readingOnly: Bool = false
) -> String { ) -> String {
let arrEndingPunctuation = ["", "", "", "", "", "", "", ""]
let whiteList = "你他妳她祢衪它牠再在" let whiteList = "你他妳她祢衪它牠再在"
var arrNodes: [Megrez.NodeAnchor] = [] var arrNodes: [Megrez.NodeAnchor] = []
var intLength = 0 var intLength = 0
for theNodeAnchor in walkedAnchors { for theNodeAnchor in walkedAnchors {
arrNodes.append(theNodeAnchor) arrNodes.append(theNodeAnchor)
intLength += theNodeAnchor.spanningLength intLength += theNodeAnchor.spanLength
if intLength >= cursorIndex { if intLength >= cursorIndex {
break break
} }
@ -146,9 +145,8 @@ extension vChewing {
arrNodes = Array(arrNodes.reversed()) arrNodes = Array(arrNodes.reversed())
guard let kvCurrent = arrNodes[0].node?.currentKeyValue, let kvCurrent = arrNodes[0].node.currentPair
!arrEndingPunctuation.contains(kvCurrent.value) guard !kvCurrent.key.contains("_") else {
else {
return "" return ""
} }
@ -173,20 +171,18 @@ extension vChewing {
} }
if arrNodes.count >= 2, if arrNodes.count >= 2,
let kvPreviousThisOne = arrNodes[1].node?.currentKeyValue, !kvPrevious.key.contains("_"),
!arrEndingPunctuation.contains(kvPrevious.value),
kvPrevious.key.split(separator: "-").count == kvPrevious.value.count kvPrevious.key.split(separator: "-").count == kvPrevious.value.count
{ {
kvPrevious = kvPreviousThisOne kvPrevious = arrNodes[1].node.currentPair
readingStack = kvPrevious.key + readingStack readingStack = kvPrevious.key + readingStack
} }
if arrNodes.count >= 3, if arrNodes.count >= 3,
let kvAnteriorThisOne = arrNodes[2].node?.currentKeyValue, !kvAnterior.key.contains("_"),
!arrEndingPunctuation.contains(kvAnterior.value),
kvAnterior.key.split(separator: "-").count == kvAnterior.value.count kvAnterior.key.split(separator: "-").count == kvAnterior.value.count
{ {
kvAnterior = kvAnteriorThisOne kvAnterior = arrNodes[2].node.currentPair
readingStack = kvAnterior.key + readingStack readingStack = kvAnterior.key + readingStack
} }
@ -290,6 +286,18 @@ extension vChewing.LMUserOverride {
} }
} }
public func clearData(withURL fileURL: URL) {
mutLRUMap = .init()
mutLRUList = .init()
do {
let nullData = "{}"
try nullData.write(to: fileURL, atomically: false, encoding: .utf8)
} catch {
IME.prtDebugIntel("UOM Error: Unable to clear data. Details: \(error)")
return
}
}
public func saveData(toURL fileURL: URL) { public func saveData(toURL fileURL: URL) {
let encoder = JSONEncoder() let encoder = JSONEncoder()
do { do {

View File

@ -224,39 +224,60 @@ enum mgrLangModel {
// Swift appendingPathComponent URL .path // Swift appendingPathComponent URL .path
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userPhrasesDataURL(_ mode: InputMode) -> URL { static func userPhrasesDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "userdata-cht.txt" : "userdata-chs.txt" let fileName = (mode == InputMode.imeModeCHT) ? "userdata-cht.txt" : "userdata-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName) return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
} }
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userSymbolDataURL(_ mode: InputMode) -> URL { static func userSymbolDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "usersymbolphrases-cht.txt" : "usersymbolphrases-chs.txt" let fileName = (mode == InputMode.imeModeCHT) ? "usersymbolphrases-cht.txt" : "usersymbolphrases-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName) return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
} }
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userAssociatesDataURL(_ mode: InputMode) -> URL { static func userAssociatesDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "associatedPhrases-cht.txt" : "associatedPhrases-chs.txt" let fileName = (mode == InputMode.imeModeCHT) ? "associatedPhrases-cht.txt" : "associatedPhrases-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName) return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
} }
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userFilteredDataURL(_ mode: InputMode) -> URL { static func userFilteredDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "exclude-phrases-cht.txt" : "exclude-phrases-chs.txt" let fileName = (mode == InputMode.imeModeCHT) ? "exclude-phrases-cht.txt" : "exclude-phrases-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName) return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
} }
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userReplacementsDataURL(_ mode: InputMode) -> URL { static func userReplacementsDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "phrases-replacement-cht.txt" : "phrases-replacement-chs.txt" let fileName = (mode == InputMode.imeModeCHT) ? "phrases-replacement-cht.txt" : "phrases-replacement-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName) return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
} }
/// 使
/// - Returns: URL
static func userSymbolNodeDataURL() -> URL { static func userSymbolNodeDataURL() -> URL {
let fileName = "symbols.dat" let fileName = "symbols.dat"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName) return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
} }
/// 使使
/// ~/Library/Application Support/vChewing/使
/// - Parameter mode:
/// - Returns: URL
static func userOverrideModelDataURL(_ mode: InputMode) -> URL { static func userOverrideModelDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "override-model-data-cht.dat" : "override-model-data-chs.dat" let fileName = (mode == InputMode.imeModeCHT) ? "override-model-data-cht.dat" : "override-model-data-chs.dat"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName) return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: true)).appendingPathComponent(fileName)
} }
// MARK: - 使 // MARK: - 使
@ -463,4 +484,15 @@ enum mgrLangModel {
break break
} }
} }
static func clearUserOverrideModelData(_ mode: InputMode = .imeModeNULL) {
switch mode {
case .imeModeCHS:
gUserOverrideModelCHS.clearData(withURL: userOverrideModelDataURL(InputMode.imeModeCHS))
case .imeModeCHT:
gUserOverrideModelCHT.clearData(withURL: userOverrideModelDataURL(InputMode.imeModeCHT))
case .imeModeNULL:
break
}
}
} }

View File

@ -25,89 +25,106 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez { extension Megrez {
/// ///
public class Compositor { public class Compositor: Grid {
///
public enum TypingDirection { case front, rear }
/// ///
private let kDroppedPathScore: Double = -999 private let kDroppedPathScore: Double = -999
/// ///
private var mutCursorIndex: Int = 0 public var cursor: Int = 0 { didSet { cursor = max(0, min(cursor, readings.count)) } }
/// ///
private var mutReadings: [String] = [] private(set) var readings: [String] = []
///
private var mutGrid: Grid = .init()
/// 使 /// 使
private var mutLM: LanguageModelProtocol private var langModel: LangModelProtocol
/// 0
private(set) var cursorRegionMap: [Int: Int] = .init()
private(set) var walkedAnchors: [Megrez.NodeAnchor] = [] //
///
public var maxBuildSpanLength: Int { mutGrid.maxBuildSpanLength }
/// ///
public var joinSeparator: String = "" public var joinSeparator: String = "-"
///
public var cursorIndex: Int {
get { mutCursorIndex }
set { mutCursorIndex = (newValue < 0) ? 0 : min(newValue, mutReadings.count) }
}
///
public var isEmpty: Bool { mutGrid.isEmpty }
///
public var grid: Grid { mutGrid }
/// ///
public var length: Int { mutReadings.count } public var length: Int { readings.count }
///
public var readings: [String] { mutReadings } ///
/// - Parameter direction:
/// - Returns:
@discardableResult public func jumpCursorBySpan(to direction: TypingDirection) -> Bool {
switch direction {
case .front:
if cursor == width { return false }
case .rear:
if cursor == 0 { return false }
}
guard let currentRegion = cursorRegionMap[cursor] else { return false }
let aRegionForward = max(currentRegion - 1, 0)
let currentRegionBorderRear: Int = walkedAnchors[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, +)
case .rear:
cursor = walkedAnchors[0..<aRegionForward].map(\.spanLength).reduce(0, +)
}
default:
switch direction {
case .front:
cursor = currentRegionBorderRear + walkedAnchors[currentRegion].spanLength
case .rear:
cursor = currentRegionBorderRear
}
}
return true
}
/// ///
/// - Parameters: /// - Parameters:
/// - lm: Megrez.LanguageModel /// - lm: Megrez.LangModel
/// - length: 10 /// - length: 10
/// - separator: /// - separator:
public init(lm: LanguageModelProtocol, length: Int = 10, separator: String = "") { public init(lm: LangModelProtocol, length: Int = 10, separator: String = "-") {
mutLM = lm langModel = lm
mutGrid = .init(spanLength: abs(length)) // super.init(spanLength: abs(length)) //
joinSeparator = separator joinSeparator = separator
} }
/// ///
public func clear() { override public func clear() {
mutCursorIndex = 0 super.clear()
mutReadings.removeAll() cursor = 0
mutGrid.clear() readings.removeAll()
walkedAnchors.removeAll()
} }
/// ///
/// - Parameters: /// - Parameters:
/// - reading: /// - reading:
public func insertReadingAtCursor(reading: String) { @discardableResult public func insertReading(_ reading: String) -> Bool {
mutReadings.insert(reading, at: mutCursorIndex) guard !reading.isEmpty, langModel.hasUnigramsFor(key: reading) else { return false }
mutGrid.expandGridByOneAt(location: mutCursorIndex) readings.insert(reading, at: cursor)
build() resizeGridByOneAt(location: cursor, to: .expand)
mutCursorIndex += 1
}
///
/// Rear
@discardableResult public func deleteReadingAtTheRearOfCursor() -> Bool {
if mutCursorIndex == 0 {
return false
}
mutReadings.remove(at: mutCursorIndex - 1)
mutCursorIndex -= 1
mutGrid.shrinkGridByOneAt(location: mutCursorIndex)
build() build()
cursor += 1
return true return true
} }
/// ///
/// Front ///
@discardableResult public func deleteReadingToTheFrontOfCursor() -> Bool { /// RearFront
if mutCursorIndex == mutReadings.count { /// - Parameter direction:
/// - Returns:
@discardableResult public func dropReading(direction: TypingDirection) -> Bool {
let isBackSpace = direction == .rear
if cursor == (isBackSpace ? 0 : readings.count) {
return false return false
} }
readings.remove(at: cursor - (isBackSpace ? 1 : 0))
mutReadings.remove(at: mutCursorIndex) cursor -= (isBackSpace ? 1 : 0)
mutGrid.shrinkGridByOneAt(location: mutCursorIndex) resizeGridByOneAt(location: cursor, to: .shrink)
build() build()
return true return true
} }
@ -118,98 +135,84 @@ extension Megrez {
/// ///
@discardableResult public func removeHeadReadings(count: Int) -> Bool { @discardableResult public func removeHeadReadings(count: Int) -> Bool {
let count = abs(count) // let count = abs(count) //
if count > length { if count > length { return false }
return false
}
for _ in 0..<count { for _ in 0..<count {
if mutCursorIndex > 0 { cursor = max(cursor - 1, 0)
mutCursorIndex -= 1 if !readings.isEmpty {
} readings.removeFirst()
if !mutReadings.isEmpty { resizeGridByOneAt(location: 0, to: .shrink)
mutReadings.removeFirst()
mutGrid.shrinkGridByOneAt(location: 0)
} }
build() build()
} }
return true return true
} }
// MARK: - Walker
/// ///
/// - Parameters: /// - Returns:
/// - location: @discardableResult public func walk() -> [NodeAnchor] {
/// - accumulatedScore: 0 let newLocation = width
/// - joinedPhrase: 使 //
/// - longPhrases: 使 walkedAnchors = Array(
public func walk( reverseWalk(at: newLocation).reversed()
at location: Int = 0, ).lazy.filter { !$0.isEmpty }
score accumulatedScore: Double = 0.0, updateCursorJumpingTables(walkedAnchors)
joinedPhrase: String = "", return walkedAnchors
longPhrases: [String] = .init()
) -> [NodeAnchor] {
let newLocation = (mutGrid.width) - abs(location) //
return Array(
reverseWalk(
at: newLocation, score: accumulatedScore,
joinedPhrase: joinedPhrase, longPhrases: longPhrases
).reversed())
} }
/// // MARK: - Private functions
///
/// - Parameters: /// - Parameters:
/// - location: /// - location:
/// - accumulatedScore: 0 /// - mass: 0
/// - joinedPhrase: 使 /// - joinedPhrase: 使
/// - longPhrases: 使 /// - longPhrases: 使
public func reverseWalk( /// - Returns:
private func reverseWalk(
at location: Int, at location: Int,
score accumulatedScore: Double = 0.0, mass: Double = 0.0,
joinedPhrase: String = "", joinedPhrase: String = "",
longPhrases: [String] = .init() longPhrases: [String] = .init()
) -> [NodeAnchor] { ) -> [NodeAnchor] {
let location = abs(location) // let location = abs(location) //
if location == 0 || location > mutGrid.width { if location == 0 || location > width {
return .init() return .init()
} }
var paths = [[NodeAnchor]]() var paths = [[NodeAnchor]]()
var nodes = mutGrid.nodesEndingAt(location: location) let nodes = nodesEndingAt(location: location).stableSorted {
nodes = nodes.stableSorted {
$0.scoreForSort > $1.scoreForSort $0.scoreForSort > $1.scoreForSort
} }
if let nodeZero = nodes[0].node, nodeZero.score >= nodeZero.kSelectedCandidateScore { guard !nodes.isEmpty else { return .init() } //
if nodes[0].node.score >= Node.kSelectedCandidateScore {
// 使 // 使
var anchorZero = nodes[0] var theAnchor = nodes[0]
anchorZero.accumulatedScore = accumulatedScore + nodeZero.score theAnchor.mass = mass + nodes[0].node.score
var path: [NodeAnchor] = reverseWalk( var path: [NodeAnchor] = reverseWalk(
at: location - anchorZero.spanningLength, score: anchorZero.accumulatedScore at: location - theAnchor.spanLength, mass: theAnchor.mass
) )
path.insert(anchorZero, at: 0) path.insert(theAnchor, at: 0)
paths.append(path) paths.append(path)
} else if !longPhrases.isEmpty { } else if !longPhrases.isEmpty {
var path = [NodeAnchor]() var path = [NodeAnchor]()
for theAnchor in nodes { for theAnchor in nodes {
guard let theNode = theAnchor.node else { continue }
var theAnchor = theAnchor var theAnchor = theAnchor
let joinedValue = theNode.currentKeyValue.value + joinedPhrase let joinedValue = theAnchor.node.currentPair.value + joinedPhrase
// //
// /////////使 // /////////使
// //
if longPhrases.contains(joinedValue) { if longPhrases.contains(joinedValue) {
theAnchor.accumulatedScore = kDroppedPathScore theAnchor.mass = kDroppedPathScore
path.insert(theAnchor, at: 0) path.insert(theAnchor, at: 0)
paths.append(path) paths.append(path)
continue continue
} }
theAnchor.accumulatedScore = accumulatedScore + theNode.score theAnchor.mass = mass + theAnchor.node.score
path = reverseWalk( path = reverseWalk(
at: location - theAnchor.spanningLength, at: location - theAnchor.spanLength,
score: theAnchor.accumulatedScore, mass: theAnchor.mass,
joinedPhrase: (joinedValue.count >= longPhrases[0].count) ? "" : joinedValue, joinedPhrase: (joinedValue.count >= longPhrases[0].count) ? "" : joinedValue,
longPhrases: .init() longPhrases: .init()
) )
@ -219,9 +222,8 @@ extension Megrez {
} else { } else {
// //
var longPhrases = [String]() var longPhrases = [String]()
for theAnchor in nodes.lazy.filter({ $0.spanningLength > 1 }) { for theAnchor in nodes.lazy.filter({ $0.spanLength > 1 }) {
guard let theNode = theAnchor.node else { continue } longPhrases.append(theAnchor.node.currentPair.value)
longPhrases.append(theNode.currentKeyValue.value)
} }
longPhrases = longPhrases.stableSorted { longPhrases = longPhrases.stableSorted {
@ -229,12 +231,11 @@ extension Megrez {
} }
for theAnchor in nodes { for theAnchor in nodes {
var theAnchor = theAnchor var theAnchor = theAnchor
guard let theNode = theAnchor.node else { continue } theAnchor.mass = mass + theAnchor.node.score
theAnchor.accumulatedScore = accumulatedScore + theNode.score
var path = [NodeAnchor]() var path = [NodeAnchor]()
path = reverseWalk( path = reverseWalk(
at: location - theAnchor.spanningLength, score: theAnchor.accumulatedScore, at: location - theAnchor.spanLength, mass: theAnchor.mass,
joinedPhrase: (theAnchor.spanningLength > 1) ? "" : theNode.currentKeyValue.value, joinedPhrase: (theAnchor.spanLength > 1) ? "" : theAnchor.node.currentPair.value,
longPhrases: .init() longPhrases: .init()
) )
path.insert(theAnchor, at: 0) path.insert(theAnchor, at: 0)
@ -248,31 +249,29 @@ extension Megrez {
var result: [NodeAnchor] = paths[0] var result: [NodeAnchor] = paths[0]
for neta in paths.lazy.filter({ for neta in paths.lazy.filter({
$0.last!.accumulatedScore > result.last!.accumulatedScore $0.last!.mass > result.last!.mass
}) { }) {
result = neta result = neta
} }
return result return result // walk()
} }
// MARK: - Private functions
private func build() { private func build() {
let itrBegin: Int = let itrBegin: Int =
(mutCursorIndex < maxBuildSpanLength) ? 0 : mutCursorIndex - maxBuildSpanLength (cursor < maxBuildSpanLength) ? 0 : cursor - maxBuildSpanLength
let itrEnd: Int = min(mutCursorIndex + maxBuildSpanLength, mutReadings.count) let itrEnd: Int = min(cursor + maxBuildSpanLength, readings.count)
for p in itrBegin..<itrEnd { for p in itrBegin..<itrEnd {
for q in 1..<maxBuildSpanLength { for q in 1..<maxBuildSpanLength {
if p + q > itrEnd { break } if p + q > itrEnd { break }
let arrSlice = mutReadings[p..<(p + q)] let arrSlice = readings[p..<(p + q)]
let combinedReading: String = join(slice: arrSlice, separator: joinSeparator) let combinedReading: String = join(slice: arrSlice, separator: joinSeparator)
if mutGrid.hasMatchedNode(location: p, spanningLength: q, key: combinedReading) { continue } if hasMatchedNode(location: p, spanLength: q, key: combinedReading) { continue }
let unigrams: [Unigram] = mutLM.unigramsFor(key: combinedReading) let unigrams: [Unigram] = langModel.unigramsFor(key: combinedReading)
if unigrams.isEmpty { continue } if unigrams.isEmpty { continue }
let n = Node(key: combinedReading, unigrams: unigrams) let n = Node(key: combinedReading, unigrams: unigrams)
mutGrid.insertNode(node: n, location: p, spanningLength: q) insertNode(node: n, location: p, spanLength: q)
} }
} }
} }
@ -280,6 +279,20 @@ extension Megrez {
private func join(slice arrSlice: ArraySlice<String>, separator: String) -> String { private func join(slice arrSlice: ArraySlice<String>, separator: String) -> String {
arrSlice.joined(separator: separator) arrSlice.joined(separator: separator)
} }
internal func updateCursorJumpingTables(_ anchors: [NodeAnchor]) {
var cursorRegionMapDict = [Int: Int]()
var counter = 0
for (i, anchor) in anchors.enumerated() {
for _ in 0..<anchor.spanLength {
cursorRegionMapDict[counter] = i
counter += 1
}
}
cursorRegionMapDict[counter] = anchors.count
cursorRegionMapDict[-1] = 0 //
cursorRegionMap = cursorRegionMapDict
}
} }
} }

View File

@ -24,93 +24,82 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
extension Megrez { extension Megrez {
/// ///
public class Grid { public class Grid {
///
public enum ResizeBehavior { case expand, shrink }
/// ///
private var mutSpans: [Megrez.Span] private(set) var spans: [Megrez.SpanUnit]
/// ///
private var mutMaxBuildSpanLength = 10 private(set) var maxBuildSpanLength = 10
///
public var maxBuildSpanLength: Int { mutMaxBuildSpanLength }
/// ///
public var width: Int { mutSpans.count } public var width: Int { spans.count }
/// ///
public var isEmpty: Bool { mutSpans.isEmpty } public var isEmpty: Bool { spans.isEmpty }
/// ///
public init(spanLength: Int = 10) { public init(spanLength: Int = 10) {
mutMaxBuildSpanLength = spanLength maxBuildSpanLength = spanLength
mutSpans = [Megrez.Span]() spans = [Megrez.SpanUnit]()
} }
/// ///
public func clear() { public func clear() {
mutSpans.removeAll() spans.removeAll()
} }
/// ///
/// - Parameters: /// - Parameters:
/// - node: /// - node:
/// - location: /// - location:
/// - spanningLength: /// - spanLength:
public func insertNode(node: Node, location: Int, spanningLength: Int) { public func insertNode(node: Node, location: Int, spanLength: Int) {
let location = abs(location) // let location = abs(location) //
let spanningLength = abs(spanningLength) // let spanLength = abs(spanLength) //
if location >= mutSpans.count { if location >= spans.count {
let diff = location - mutSpans.count + 1 let diff = location - spans.count + 1
for _ in 0..<diff { for _ in 0..<diff {
mutSpans.append(Span()) spans.append(SpanUnit())
} }
} }
mutSpans[location].insert(node: node, length: spanningLength) spans[location].insert(node: node, length: spanLength)
} }
/// ///
/// - Parameters: /// - Parameters:
/// - location: /// - location:
/// - spanningLength: /// - spanLength:
/// - key: /// - key:
public func hasMatchedNode(location: Int, spanningLength: Int, key: String) -> Bool { public func hasMatchedNode(location: Int, spanLength: Int, key: String) -> Bool {
let location = abs(location) // let location = abs(location) //
let spanningLength = abs(spanningLength) // let spanLength = abs(spanLength) //
if location > mutSpans.count { if location > spans.count {
return false return false
} }
let n = mutSpans[location].node(length: spanningLength) let n = spans[location].nodeOf(length: spanLength)
return n != nil && key == n?.key return n != nil && key == n?.key
} }
/// ///
/// - Parameters: /// - Parameters:
/// - location: /// - location:
public func expandGridByOneAt(location: Int) { public func resizeGridByOneAt(location: Int, to behavior: ResizeBehavior) {
let location = abs(location) // let location = max(0, min(width, location)) //
mutSpans.insert(Span(), at: location) switch behavior {
if location == 0 || location == mutSpans.count { return } 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 { for i in 0..<location {
// zaps overlapping spans // zaps overlapping spans
mutSpans[i].removeNodeOfLengthGreaterThan(location - i) spans[i].dropNodesBeyond(length: location - i)
}
}
///
/// - Parameters:
/// - location:
public func shrinkGridByOneAt(location: Int) {
let location = abs(location) //
if location >= mutSpans.count {
return
}
mutSpans.remove(at: location)
for i in 0..<location {
// zaps overlapping spans
mutSpans[i].removeNodeOfLengthGreaterThan(location - i)
} }
} }
@ -120,21 +109,21 @@ extension Megrez {
public func nodesBeginningAt(location: Int) -> [NodeAnchor] { public func nodesBeginningAt(location: Int) -> [NodeAnchor] {
let location = abs(location) // let location = abs(location) //
var results = [NodeAnchor]() var results = [NodeAnchor]()
if location >= mutSpans.count { return results } if location >= spans.count { return results }
// mutSpans location 0 // spans location 0
let span = mutSpans[location] let span = spans[location]
for i in 1...maxBuildSpanLength { for i in 1...maxBuildSpanLength {
if let np = span.node(length: i) { if let np = span.nodeOf(length: i) {
results.append( results.append(
.init( .init(
node: np, node: np,
location: location, location: location,
spanningLength: i spanLength: i
) )
) )
} }
} }
return results return results //
} }
/// ///
@ -143,21 +132,21 @@ extension Megrez {
public func nodesEndingAt(location: Int) -> [NodeAnchor] { public func nodesEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) // let location = abs(location) //
var results = [NodeAnchor]() var results = [NodeAnchor]()
if mutSpans.isEmpty || location > mutSpans.count { return results } if spans.isEmpty || location > spans.count { return results }
for i in 0..<location { for i in 0..<location {
let span = mutSpans[i] let span = spans[i]
if i + span.maximumLength < location { continue } if i + span.maxLength < location { continue }
if let np = span.node(length: location - i) { if let np = span.nodeOf(length: location - i) {
results.append( results.append(
.init( .init(
node: np, node: np,
location: i, location: i,
spanningLength: location - i spanLength: location - i
) )
) )
} }
} }
return results return results //
} }
/// ///
@ -166,46 +155,76 @@ extension Megrez {
public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] { public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) // let location = abs(location) //
var results = [NodeAnchor]() var results = [NodeAnchor]()
if mutSpans.isEmpty || location > mutSpans.count { return results } if spans.isEmpty || location > spans.count { return results }
for i in 0..<location { for i in 0..<location {
let span = mutSpans[i] let span = spans[i]
if i + span.maximumLength < location { continue } if i + span.maxLength < location { continue }
for j in 1...span.maximumLength { for j in 1...span.maxLength {
if i + j < location { continue } if i + j < location { continue }
if let np = span.node(length: j) { if let np = span.nodeOf(length: j) {
results.append( results.append(
.init( .init(
node: np, node: np,
location: i, location: i,
spanningLength: location - i spanLength: location - i
) )
) )
} }
} }
} }
return results 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 nodesOverlappedAt(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: /// - Parameters:
/// - location: /// - location:
/// - value: /// - value:
@discardableResult public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor { @discardableResult public func fixNodeWithCandidate(_ pair: KeyValuePaired, at location: Int) -> NodeAnchor {
let location = abs(location) // let location = abs(location) //
var node = NodeAnchor() var node = NodeAnchor()
for nodeAnchor in nodesCrossingOrEndingAt(location: location) { for theAnchor in nodesOverlappedAt(location: location) {
guard let theNode = nodeAnchor.node else { let candidates = theAnchor.node.candidates
continue
}
let candidates = theNode.candidates
// //
theNode.resetCandidate() theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() { for (i, candidate) in candidates.enumerated() {
if candidate.value == value { if candidate == pair {
theNode.selectCandidateAt(index: i) theAnchor.node.selectCandidateAt(index: i)
node = nodeAnchor node = theAnchor
break break
} }
} }
@ -220,16 +239,13 @@ extension Megrez {
/// - overridingScore: /// - overridingScore:
public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) { public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) {
let location = abs(location) // let location = abs(location) //
for nodeAnchor in nodesCrossingOrEndingAt(location: location) { for theAnchor in nodesOverlappedAt(location: location) {
guard let theNode = nodeAnchor.node else { let candidates = theAnchor.node.candidates
continue
}
let candidates = theNode.candidates
// //
theNode.resetCandidate() theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() { for (i, candidate) in candidates.enumerated() {
if candidate.value == value { if candidate.value == value {
theNode.selectFloatingCandidateAt(index: i, score: overridingScore) theAnchor.node.selectFloatingCandidateAt(index: i, score: overridingScore)
break break
} }
} }
@ -244,29 +260,22 @@ extension Megrez.Grid {
/// GraphViz /// GraphViz
public var dumpDOT: String { public var dumpDOT: String {
var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n" var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
for (p, span) in mutSpans.enumerated() { for (p, span) in spans.enumerated() {
for ni in 0...(span.maximumLength) { for ni in 0...(span.maxLength) {
guard let np: Megrez.Node = span.node(length: ni) else { guard let np = span.nodeOf(length: ni) else { continue }
continue
}
if p == 0 { if p == 0 {
strOutput += "BOS -> \(np.currentKeyValue.value);\n" strOutput += "BOS -> \(np.currentPair.value);\n"
} }
strOutput += "\(np.currentPair.value);\n"
strOutput += "\(np.currentKeyValue.value);\n" if (p + ni) < spans.count {
let destinationSpan = spans[p + ni]
if (p + ni) < mutSpans.count { for q in 0...(destinationSpan.maxLength) {
let destinationSpan = mutSpans[p + ni] guard let dn = destinationSpan.nodeOf(length: q) else { continue }
for q in 0...(destinationSpan.maximumLength) { strOutput += np.currentPair.value + " -> " + dn.currentPair.value + ";\n"
if let dn = destinationSpan.node(length: q) {
strOutput += np.currentKeyValue.value + " -> " + dn.currentKeyValue.value + ";\n"
}
} }
} }
guard (p + ni) == spans.count else { continue }
if (p + ni) == mutSpans.count { strOutput += np.currentPair.value + " -> EOS;\n"
strOutput += np.currentKeyValue.value + " -> EOS;\n"
}
} }
} }
strOutput += "EOS;\n}\n" strOutput += "EOS;\n}\n"

View File

@ -25,25 +25,34 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez { extension Megrez {
/// ///
@frozen public struct NodeAnchor: CustomStringConvertible { @frozen public struct NodeAnchor: Hashable {
///
public var isEmpty: Bool { node.key.isEmpty }
/// ///
public var node: Node? public var node: Node = .init()
/// ///
public var location: Int = 0 public var location: Int = 0
/// ///
public var spanningLength: Int = 0 public var spanLength: Int = 0
/// ///
public var accumulatedScore: Double = 0.0 public var mass: Double = 0.0
/// ///
public var keyLength: Int { public var keyLength: Int {
node?.key.count ?? 0 isEmpty ? node.key.count : 0
}
public func hash(into hasher: inout Hasher) {
hasher.combine(node)
hasher.combine(location)
hasher.combine(spanLength)
hasher.combine(mass)
} }
/// ///
public var description: String { public var description: String {
var stream = "" var stream = ""
stream += "{@(" + String(location) + "," + String(spanningLength) + ")," stream += "{@(" + String(location) + "," + String(spanLength) + "),"
if let node = node { if node.key.isEmpty {
stream += node.description stream += node.description
} else { } else {
stream += "null" stream += "null"
@ -54,12 +63,12 @@ extension Megrez {
/// ///
public var scoreForSort: Double { public var scoreForSort: Double {
node?.score ?? 0 isEmpty ? node.score : 0
} }
} }
} }
// MARK: - DumpDOT-related functions. // MARK: - Array Extensions.
extension Array where Element == Megrez.NodeAnchor { extension Array where Element == Megrez.NodeAnchor {
/// ///
@ -70,4 +79,14 @@ extension Array where Element == Megrez.NodeAnchor {
} }
return arrOutputContent.joined(separator: "<-") return arrOutputContent.joined(separator: "<-")
} }
///
public var values: [String] {
map(\.node.currentPair.value)
}
///
public var keys: [String] {
map(\.node.currentPair.key)
}
} }

View File

@ -25,21 +25,16 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez { extension Megrez {
/// ///
@frozen public struct Span { @frozen public struct SpanUnit {
/// ///
private var mutLengthNodeMap: [Int: Megrez.Node] = [:] private var lengthNodeMap: [Int: Megrez.Node] = [:]
/// ///
private var mutMaximumLength: Int = 0 private(set) var maxLength: Int = 0
///
public var maximumLength: Int {
mutMaximumLength
}
/// ///
mutating func clear() { mutating func clear() {
mutLengthNodeMap.removeAll() lengthNodeMap.removeAll()
mutMaximumLength = 0 maxLength = 0
} }
/// ///
@ -48,37 +43,37 @@ extension Megrez {
/// - length: /// - length:
mutating func insert(node: Node, length: Int) { mutating func insert(node: Node, length: Int) {
let length = abs(length) // let length = abs(length) //
mutLengthNodeMap[length] = node lengthNodeMap[length] = node
mutMaximumLength = max(mutMaximumLength, length) maxLength = max(maxLength, length)
} }
/// ///
/// - Parameters: /// - Parameters:
/// - length: /// - length:
mutating func removeNodeOfLengthGreaterThan(_ length: Int) { mutating func dropNodesBeyond(length: Int) {
let length = abs(length) // let length = abs(length) //
if length > mutMaximumLength { return } if length > maxLength { return }
var lenMax = 0 var lenMax = 0
var removalList: [Int: Megrez.Node] = [:] var removalList: [Int: Megrez.Node] = [:]
for key in mutLengthNodeMap.keys { for key in lengthNodeMap.keys {
if key > length { if key > length {
removalList[key] = mutLengthNodeMap[key] removalList[key] = lengthNodeMap[key]
} else { } else {
lenMax = max(lenMax, key) lenMax = max(lenMax, key)
} }
} }
for key in removalList.keys { for key in removalList.keys {
mutLengthNodeMap.removeValue(forKey: key) lengthNodeMap.removeValue(forKey: key)
} }
mutMaximumLength = lenMax maxLength = lenMax
} }
/// ///
/// - Parameters: /// - Parameters:
/// - length: /// - length:
public func node(length: Int) -> Node? { public func nodeOf(length: Int) -> Node? {
// Abs() // Abs()
mutLengthNodeMap.keys.contains(abs(length)) ? mutLengthNodeMap[abs(length)] : nil lengthNodeMap.keys.contains(abs(length)) ? lengthNodeMap[abs(length)] : nil
} }
} }
} }

View File

@ -25,76 +25,86 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez { extension Megrez {
/// ///
public class Node { public class Node: Equatable, Hashable {
/// public static func == (lhs: Megrez.Node, rhs: Megrez.Node) -> Bool {
private var mutKey: String = "" lhs.key == rhs.key && lhs.score == rhs.score && lhs.unigrams == rhs.unigrams && lhs.bigrams == rhs.bigrams
/// && lhs.candidates == rhs.candidates && lhs.valueUnigramIndexMap == rhs.valueUnigramIndexMap
private var mutScore: Double = 0 && lhs.precedingBigramMap == rhs.precedingBigramMap && lhs.isCandidateFixed == rhs.isCandidateFixed
/// && lhs.selectedUnigramIndex == rhs.selectedUnigramIndex
private var mutUnigrams: [Unigram]
///
private var mutBigrams: [Bigram]
///
private var mutCandidates: [KeyValuePaired] = []
/// 調
private var mutValueUnigramIndexMap: [String: Int] = [:]
///
private var mutPrecedingBigramMap: [KeyValuePaired: [Megrez.Bigram]] = [:]
///
private var mutCandidateFixed: Bool = false
///
private var mutSelectedUnigramIndex: Int = 0
///
public let kSelectedCandidateScore: Double = 99
///
public var description: String {
"(node,key:\(mutKey),fixed:\(mutCandidateFixed ? "true" : "false"),selected:\(mutSelectedUnigramIndex),\(mutUnigrams))"
} }
/// public func hash(into hasher: inout Hasher) {
public var candidates: [KeyValuePaired] { mutCandidates } hasher.combine(key)
/// hasher.combine(score)
public var isCandidateFixed: Bool { mutCandidateFixed } hasher.combine(unigrams)
hasher.combine(bigrams)
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 var unigrams: [Unigram]
///
private var bigrams: [Bigram]
///
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 key: String { mutKey }
///
public var score: Double { mutScore }
/// ///
public var currentKeyValue: KeyValuePaired { public var currentPair: KeyValuePaired {
mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePaired() : mutCandidates[mutSelectedUnigramIndex] selectedUnigramIndex >= unigrams.count ? KeyValuePaired() : candidates[selectedUnigramIndex]
} }
/// ///
public var highestUnigramScore: Double { mutUnigrams.isEmpty ? 0.0 : mutUnigrams[0].score } public var highestUnigramScore: Double { unigrams.isEmpty ? 0.0 : unigrams[0].score }
/// ///
/// - Parameters: /// - Parameters:
/// - key: /// - key:
/// - unigrams: /// - unigrams:
/// - bigrams: /// - bigrams:
public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) { public init(key: String = "", unigrams: [Megrez.Unigram] = [], bigrams: [Megrez.Bigram] = []) {
mutKey = key self.key = key
mutUnigrams = unigrams self.unigrams = unigrams
mutBigrams = bigrams self.bigrams = bigrams
mutUnigrams.sort { self.unigrams.sort {
$0.score > $1.score $0.score > $1.score
} }
if !mutUnigrams.isEmpty { if !self.unigrams.isEmpty {
mutScore = mutUnigrams[0].score score = unigrams[0].score
} }
for (i, gram) in mutUnigrams.enumerated() { for (i, gram) in self.unigrams.enumerated() {
mutValueUnigramIndexMap[gram.keyValue.value] = i valueUnigramIndexMap[gram.keyValue.value] = i
mutCandidates.append(gram.keyValue) candidates.append(gram.keyValue)
} }
for gram in bigrams.lazy.filter({ [self] in for gram in bigrams.lazy.filter({ [self] in
mutPrecedingBigramMap.keys.contains($0.precedingKeyValue) precedingBigramMap.keys.contains($0.precedingKeyValue)
}) { }) {
mutPrecedingBigramMap[gram.precedingKeyValue]?.append(gram) precedingBigramMap[gram.precedingKeyValue]?.append(gram)
} }
} }
@ -102,22 +112,22 @@ extension Megrez {
/// - Parameters: /// - Parameters:
/// - precedingKeyValues: /// - precedingKeyValues:
public func primeNodeWith(precedingKeyValues: [KeyValuePaired]) { public func primeNodeWith(precedingKeyValues: [KeyValuePaired]) {
var newIndex = mutSelectedUnigramIndex var newIndex = selectedUnigramIndex
var max = mutScore var max = score
if !isCandidateFixed { if !isCandidateFixed {
for neta in precedingKeyValues { for neta in precedingKeyValues {
let bigrams = mutPrecedingBigramMap[neta] ?? [] let bigrams = precedingBigramMap[neta] ?? []
for bigram in bigrams.lazy.filter({ [self] in for bigram in bigrams.lazy.filter({ [self] in
$0.score > max && mutValueUnigramIndexMap.keys.contains($0.keyValue.value) $0.score > max && valueUnigramIndexMap.keys.contains($0.keyValue.value)
}) { }) {
newIndex = mutValueUnigramIndexMap[bigram.keyValue.value] ?? newIndex newIndex = valueUnigramIndexMap[bigram.keyValue.value] ?? newIndex
max = bigram.score max = bigram.score
} }
} }
} }
mutScore = max score = max
mutSelectedUnigramIndex = newIndex selectedUnigramIndex = newIndex
} }
/// ///
@ -126,17 +136,17 @@ extension Megrez {
/// - fix: /// - fix:
public func selectCandidateAt(index: Int = 0, fix: Bool = false) { public func selectCandidateAt(index: Int = 0, fix: Bool = false) {
let index = abs(index) let index = abs(index)
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index selectedUnigramIndex = index >= unigrams.count ? 0 : index
mutCandidateFixed = fix isCandidateFixed = fix
mutScore = kSelectedCandidateScore score = Megrez.Node.kSelectedCandidateScore
} }
/// ///
public func resetCandidate() { public func resetCandidate() {
mutSelectedUnigramIndex = 0 selectedUnigramIndex = 0
mutCandidateFixed = false isCandidateFixed = false
if !mutUnigrams.isEmpty { if !unigrams.isEmpty {
mutScore = mutUnigrams[0].score score = unigrams[0].score
} }
} }
@ -146,16 +156,26 @@ extension Megrez {
/// - score: /// - score:
public func selectFloatingCandidateAt(index: Int, score: Double) { public func selectFloatingCandidateAt(index: Int, score: Double) {
let index = abs(index) // let index = abs(index) //
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index selectedUnigramIndex = index >= unigrams.count ? 0 : index
mutCandidateFixed = false isCandidateFixed = false
mutScore = score self.score = score
} }
/// ///
/// - Parameters: /// - Parameters:
/// - candidate: /// - candidate:
public func scoreFor(candidate: String) -> Double { public func scoreFor(candidate: String) -> Double {
for unigram in mutUnigrams.lazy.filter({ $0.keyValue.value == candidate }) { 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 unigram.score
} }
return 0.0 return 0.0

View File

@ -23,7 +23,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
public protocol LanguageModelProtocol { public protocol LangModelProtocol {
/// ///
func unigramsFor(key: String) -> [Megrez.Unigram] func unigramsFor(key: String) -> [Megrez.Unigram]
@ -36,7 +36,7 @@ public protocol LanguageModelProtocol {
extension Megrez { extension Megrez {
/// 使 /// 使
open class LanguageModel: LanguageModelProtocol { open class LangModel: LangModelProtocol {
public init() {} public init() {}
// Swift // Swift

View File

@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez { extension Megrez {
/// ///
@frozen public struct Bigram: Equatable, CustomStringConvertible { @frozen public struct Bigram: Equatable, CustomStringConvertible, Hashable {
/// ///
public var keyValue: KeyValuePaired public var keyValue: KeyValuePaired
/// ///
@ -61,7 +61,7 @@ extension Megrez {
public static func < (lhs: Bigram, rhs: Bigram) -> Bool { public static func < (lhs: Bigram, rhs: Bigram) -> Bool {
lhs.precedingKeyValue < rhs.precedingKeyValue lhs.precedingKeyValue < rhs.precedingKeyValue
|| (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue)) || (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score))
} }
} }
} }

View File

@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
extension Megrez { extension Megrez {
/// ///
@frozen public struct Unigram: Equatable, CustomStringConvertible { @frozen public struct Unigram: Equatable, CustomStringConvertible, Hashable {
/// ///
public var keyValue: KeyValuePaired public var keyValue: KeyValuePaired
/// ///
@ -54,7 +54,7 @@ extension Megrez {
} }
public static func < (lhs: Unigram, rhs: Unigram) -> Bool { public static func < (lhs: Unigram, rhs: Unigram) -> Bool {
lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue) lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.score < rhs.score)
} }
} }
} }

View File

@ -52,7 +52,7 @@ extension Megrez {
} }
public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { public static func == (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {
lhs.key.count == rhs.key.count && lhs.value == rhs.value lhs.key == rhs.key && lhs.value == rhs.value
} }
public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool { public static func < (lhs: KeyValuePaired, rhs: KeyValuePaired) -> Bool {

View File

@ -32,24 +32,18 @@ let kConnectionName = "vChewing_1_Connection"
switch max(CommandLine.arguments.count - 1, 0) { switch max(CommandLine.arguments.count - 1, 0) {
case 0: break case 0: break
case 1, 2: case 1, 2:
do { switch CommandLine.arguments[1] {
switch CommandLine.arguments[1] { case "install":
case "install": if CommandLine.arguments[1] == "install" {
do { let exitCode = IME.registerInputMethod()
if CommandLine.arguments[1] == "install" { exit(exitCode)
let exitCode = IME.registerInputMethod() }
exit(exitCode) case "uninstall":
} if CommandLine.arguments[1] == "uninstall" {
} let exitCode = IME.uninstall(isSudo: IME.isSudoMode)
case "uninstall": exit(exitCode)
do { }
if CommandLine.arguments[1] == "uninstall" { default: break
let exitCode = IME.uninstall(isSudo: IME.isSudoMode)
exit(exitCode)
}
}
default: break
}
} }
exit(0) exit(0)
default: exit(0) default: exit(0)

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "Loading CHT Core Dict..."; "Loading CHT Core Dict..." = "Loading CHT Core Dict...";
"Core Dict loading complete." = "Core Dict loading complete."; "Core Dict loading complete." = "Core Dict loading complete.";
"Optimize Memorized Phrases" = "Optimize Memorized Phrases"; "Optimize Memorized Phrases" = "Optimize Memorized Phrases";
"Clear Memorized Phrases" = "Clear Memorized Phrases";
// The followings are the category names used in the Symbol menu. // The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "CommonSymbols"; "catCommonSymbols" = "CommonSymbols";
@ -146,7 +147,7 @@
"Push the cursor in front of the phrase after selection" = "Push the cursor in front of the phrase after selection"; "Push the cursor in front of the phrase after selection" = "Push the cursor in front of the phrase after selection";
"Secondary Pinyin with Numeral Intonation" = "Secondary Pinyin with Numeral Intonation"; "Secondary Pinyin with Numeral Intonation" = "Secondary Pinyin with Numeral Intonation";
"Selection Keys:" = "Selection Keys:"; "Selection Keys:" = "Selection Keys:";
"Show Hanyu-Pinyin in the inline composition buffer" = "Show Hanyu-Pinyin in the inline composition buffer"; "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"; "Show page buttons in candidate window" = "Show page buttons in candidate window";
"Simplified Chinese" = "Simplified Chinese"; "Simplified Chinese" = "Simplified Chinese";
"Space & ESC Key:" = "Space & ESC Key:"; "Space & ESC Key:" = "Space & ESC Key:";

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "Loading CHT Core Dict..."; "Loading CHT Core Dict..." = "Loading CHT Core Dict...";
"Core Dict loading complete." = "Core Dict loading complete."; "Core Dict loading complete." = "Core Dict loading complete.";
"Optimize Memorized Phrases" = "Optimize Memorized Phrases"; "Optimize Memorized Phrases" = "Optimize Memorized Phrases";
"Clear Memorized Phrases" = "Clear Memorized Phrases";
// The followings are the category names used in the Symbol menu. // The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "CommonSymbols"; "catCommonSymbols" = "CommonSymbols";
@ -146,7 +147,7 @@
"Push the cursor in front of the phrase after selection" = "Push the cursor in front of the phrase after selection"; "Push the cursor in front of the phrase after selection" = "Push the cursor in front of the phrase after selection";
"Secondary Pinyin with Numeral Intonation" = "Secondary Pinyin with Numeral Intonation"; "Secondary Pinyin with Numeral Intonation" = "Secondary Pinyin with Numeral Intonation";
"Selection Keys:" = "Selection Keys:"; "Selection Keys:" = "Selection Keys:";
"Show Hanyu-Pinyin in the inline composition buffer" = "Show Hanyu-Pinyin in the inline composition buffer"; "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"; "Show page buttons in candidate window" = "Show page buttons in candidate window";
"Simplified Chinese" = "Simplified Chinese"; "Simplified Chinese" = "Simplified Chinese";
"Space & ESC Key:" = "Space & ESC Key:"; "Space & ESC Key:" = "Space & ESC Key:";

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "繁体中国語核心辞書読込中…"; "Loading CHT Core Dict..." = "繁体中国語核心辞書読込中…";
"Core Dict loading complete." = "核心辞書読込完了"; "Core Dict loading complete." = "核心辞書読込完了";
"Optimize Memorized Phrases" = "臨時記憶資料を整う"; "Optimize Memorized Phrases" = "臨時記憶資料を整う";
"Clear Memorized Phrases" = "臨時記憶資料を削除";
// The followings are the category names used in the Symbol menu. // The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "常用"; "catCommonSymbols" = "常用";
@ -146,7 +147,7 @@
"Push the cursor in front of the phrase after selection" = "候補選択の直後、すぐカーソルを単語の向こうに推し進める"; "Push the cursor in front of the phrase after selection" = "候補選択の直後、すぐカーソルを単語の向こうに推し進める";
"Secondary Pinyin with Numeral Intonation" = "国音二式 (ローマ字+数字音調)"; "Secondary Pinyin with Numeral Intonation" = "国音二式 (ローマ字+数字音調)";
"Selection Keys:" = "言選り用キー:"; "Selection Keys:" = "言選り用キー:";
"Show Hanyu-Pinyin in the inline composition buffer" = "弁音合併入力(入力緩衝列で代わりに漢語弁音の音読み"; "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" = "簡体中国語";
"Space & ESC Key:" = "ESC と Space:"; "Space & ESC Key:" = "ESC と Space:";

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "载入繁体中文核心辞典…"; "Loading CHT Core Dict..." = "载入繁体中文核心辞典…";
"Core Dict loading complete." = "核心辞典载入完毕"; "Core Dict loading complete." = "核心辞典载入完毕";
"Optimize Memorized Phrases" = "精简临时记忆语汇资料"; "Optimize Memorized Phrases" = "精简临时记忆语汇资料";
"Clear Memorized Phrases" = "清除临时记忆语汇资料";
// The followings are the category names used in the Symbol menu. // The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "常用"; "catCommonSymbols" = "常用";
@ -147,7 +148,7 @@
"Push the cursor in front of the phrase after selection" = "在选字后将游标置于该字词的前方"; "Push the cursor in front of the phrase after selection" = "在选字后将游标置于该字词的前方";
"Secondary Pinyin with Numeral Intonation" = "国音二式+数字标调"; "Secondary Pinyin with Numeral Intonation" = "国音二式+数字标调";
"Selection Keys:" = "选字键:"; "Selection Keys:" = "选字键:";
"Show Hanyu-Pinyin in the inline composition buffer" = "拼音并击模式(组字区内看到的是汉语拼音)"; "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" = "简体中文";
"Space & ESC Key:" = "ESC 与空格键:"; "Space & ESC Key:" = "ESC 与空格键:";

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "載入繁體中文核心辭典…"; "Loading CHT Core Dict..." = "載入繁體中文核心辭典…";
"Core Dict loading complete." = "核心辭典載入完畢"; "Core Dict loading complete." = "核心辭典載入完畢";
"Optimize Memorized Phrases" = "精簡臨時記憶語彙資料"; "Optimize Memorized Phrases" = "精簡臨時記憶語彙資料";
"Clear Memorized Phrases" = "清除臨時記憶語彙資料";
// The followings are the category names used in the Symbol menu. // The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "常用"; "catCommonSymbols" = "常用";
@ -146,7 +147,7 @@
"Push the cursor in front of the phrase after selection" = "在選字後將游標置於該字詞的前方"; "Push the cursor in front of the phrase after selection" = "在選字後將游標置於該字詞的前方";
"Secondary Pinyin with Numeral Intonation" = "國音二式+數字標調"; "Secondary Pinyin with Numeral Intonation" = "國音二式+數字標調";
"Selection Keys:" = "選字鍵:"; "Selection Keys:" = "選字鍵:";
"Show Hanyu-Pinyin in the inline composition buffer" = "拼音並擊模式(組字區內看到的是漢語拼音)"; "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" = "簡體中文";
"Space & ESC Key:" = "ESC 與空格鍵:"; "Space & ESC Key:" = "ESC 與空格鍵:";

View File

@ -40,7 +40,7 @@ public class CandidateKeyLabel: NSObject {
public protocol ctlCandidateDelegate: AnyObject { public protocol ctlCandidateDelegate: AnyObject {
func candidateCountForController(_ controller: ctlCandidate) -> Int func candidateCountForController(_ controller: ctlCandidate) -> Int
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int) func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
-> String -> (String, String)
func ctlCandidate( func ctlCandidate(
_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int _ controller: ctlCandidate, didSelectCandidateAtIndex index: Int
) )

View File

@ -162,146 +162,142 @@ private class vwrCandidateUniversal: NSView {
switch isVerticalLayout { switch isVerticalLayout {
case true: case true:
do { var accuHeight: CGFloat = 0
var accuHeight: CGFloat = 0 for (index, elementHeight) in elementHeights.enumerated() {
for (index, elementHeight) in elementHeights.enumerated() { let currentHeight = elementHeight
let currentHeight = elementHeight let rctCandidateArea = NSRect(
let rctCandidateArea = NSRect( x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding
x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding )
) let rctLabel = NSRect(
let rctLabel = NSRect( x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth,
x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth, height: keyLabelHeight * 2.0
height: keyLabelHeight * 2.0 )
) let rctCandidatePhrase = NSRect(
let rctCandidatePhrase = NSRect( x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1,
x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1, width: windowWidth - keyLabelWidth, height: candidateTextHeight
width: windowWidth - keyLabelWidth, height: candidateTextHeight )
)
var activeCandidateIndexAttr = keyLabelAttrDict var activeCandidateIndexAttr = keyLabelAttrDict
var activeCandidateAttr = candidateAttrDict var activeCandidateAttr = candidateAttrDict
if index == highlightedIndex { if index == highlightedIndex {
let colorBlendAmount: CGFloat = IME.isDarkMode() ? 0.25 : 0 let colorBlendAmount: CGFloat = IME.isDarkMode() ? 0.25 : 0
// The background color of the highlightened candidate // The background color of the highlightened candidate
switch IME.currentInputMode {
case InputMode.imeModeCHS:
NSColor.systemRed.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor
)!
.setFill()
case InputMode.imeModeCHT:
NSColor.systemBlue.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor
)!
.setFill()
default:
NSColor.alternateSelectedControlColor.setFill()
}
// Highlightened index text color
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
.withAlphaComponent(0.84)
// Highlightened phrase text color
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
} else {
NSColor.controlBackgroundColor.setFill()
}
switch IME.currentInputMode { switch IME.currentInputMode {
case InputMode.imeModeCHS: case InputMode.imeModeCHS:
if #available(macOS 12.0, *) { NSColor.systemRed.blended(
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject withFraction: colorBlendAmount,
} of: NSColor.controlBackgroundColor
)!
.setFill()
case InputMode.imeModeCHT: case InputMode.imeModeCHT:
if #available(macOS 12.0, *) { NSColor.systemBlue.blended(
activeCandidateAttr[.languageIdentifier] = withFraction: colorBlendAmount,
(mgrPrefs.shiftJISShinjitaiOutputEnabled || mgrPrefs.chineseConversionEnabled) of: NSColor.controlBackgroundColor
? "ja" as AnyObject : "zh-Hant" as AnyObject )!
} .setFill()
default: default:
break NSColor.alternateSelectedControlColor.setFill()
} }
NSBezierPath.fill(rctCandidateArea) // Highlightened index text color
(keyLabels[index] as NSString).draw( activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
in: rctLabel, withAttributes: activeCandidateIndexAttr .withAlphaComponent(0.84)
) // Highlightened phrase text color
(displayedCandidates[index] as NSString).draw( activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
in: rctCandidatePhrase, withAttributes: activeCandidateAttr } else {
) NSColor.controlBackgroundColor.setFill()
accuHeight += currentHeight
} }
switch IME.currentInputMode {
case InputMode.imeModeCHS:
if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject
}
case InputMode.imeModeCHT:
if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] =
(mgrPrefs.shiftJISShinjitaiOutputEnabled || mgrPrefs.chineseConversionEnabled)
? "ja" as AnyObject : "zh-Hant" as AnyObject
}
default:
break
}
NSBezierPath.fill(rctCandidateArea)
(keyLabels[index] as NSString).draw(
in: rctLabel, withAttributes: activeCandidateIndexAttr
)
(displayedCandidates[index] as NSString).draw(
in: rctCandidatePhrase, withAttributes: activeCandidateAttr
)
accuHeight += currentHeight
} }
case false: case false:
do { var accuWidth: CGFloat = 0
var accuWidth: CGFloat = 0 for (index, elementWidth) in elementWidths.enumerated() {
for (index, elementWidth) in elementWidths.enumerated() { let currentWidth = elementWidth
let currentWidth = elementWidth let rctCandidateArea = NSRect(
let rctCandidateArea = NSRect( x: accuWidth, y: 0.0, width: currentWidth + 1.0,
x: accuWidth, y: 0.0, width: currentWidth + 1.0, height: candidateTextHeight + cellPadding
height: candidateTextHeight + cellPadding )
) let rctLabel = NSRect(
let rctLabel = NSRect( x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2, width: keyLabelWidth,
x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2, width: keyLabelWidth, height: keyLabelHeight * 2.0
height: keyLabelHeight * 2.0 )
) let rctCandidatePhrase = NSRect(
let rctCandidatePhrase = NSRect( x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2 - 1,
x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2 - 1, width: currentWidth - keyLabelWidth,
width: currentWidth - keyLabelWidth, height: candidateTextHeight
height: candidateTextHeight )
)
var activeCandidateIndexAttr = keyLabelAttrDict var activeCandidateIndexAttr = keyLabelAttrDict
var activeCandidateAttr = candidateAttrDict var activeCandidateAttr = candidateAttrDict
if index == highlightedIndex { if index == highlightedIndex {
let colorBlendAmount: CGFloat = IME.isDarkMode() ? 0.25 : 0 let colorBlendAmount: CGFloat = IME.isDarkMode() ? 0.25 : 0
// The background color of the highlightened candidate // The background color of the highlightened candidate
switch IME.currentInputMode {
case InputMode.imeModeCHS:
NSColor.systemRed.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor
)!
.setFill()
case InputMode.imeModeCHT:
NSColor.systemBlue.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor
)!
.setFill()
default:
NSColor.alternateSelectedControlColor.setFill()
}
// Highlightened index text color
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
.withAlphaComponent(0.84)
// Highlightened phrase text color
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
} else {
NSColor.controlBackgroundColor.setFill()
}
switch IME.currentInputMode { switch IME.currentInputMode {
case InputMode.imeModeCHS: case InputMode.imeModeCHS:
if #available(macOS 12.0, *) { NSColor.systemRed.blended(
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject withFraction: colorBlendAmount,
} of: NSColor.controlBackgroundColor
)!
.setFill()
case InputMode.imeModeCHT: case InputMode.imeModeCHT:
if #available(macOS 12.0, *) { NSColor.systemBlue.blended(
activeCandidateAttr[.languageIdentifier] = withFraction: colorBlendAmount,
(mgrPrefs.shiftJISShinjitaiOutputEnabled || mgrPrefs.chineseConversionEnabled) of: NSColor.controlBackgroundColor
? "ja" as AnyObject : "zh-Hant" as AnyObject )!
} .setFill()
default: default:
break NSColor.alternateSelectedControlColor.setFill()
} }
NSBezierPath.fill(rctCandidateArea) // Highlightened index text color
(keyLabels[index] as NSString).draw( activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
in: rctLabel, withAttributes: activeCandidateIndexAttr .withAlphaComponent(0.84)
) // Highlightened phrase text color
(displayedCandidates[index] as NSString).draw( activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
in: rctCandidatePhrase, withAttributes: activeCandidateAttr } else {
) NSColor.controlBackgroundColor.setFill()
accuWidth += currentWidth + 1.0
} }
switch IME.currentInputMode {
case InputMode.imeModeCHS:
if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject
}
case InputMode.imeModeCHT:
if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] =
(mgrPrefs.shiftJISShinjitaiOutputEnabled || mgrPrefs.chineseConversionEnabled)
? "ja" as AnyObject : "zh-Hant" as AnyObject
}
default:
break
}
NSBezierPath.fill(rctCandidateArea)
(keyLabels[index] as NSString).draw(
in: rctLabel, withAttributes: activeCandidateIndexAttr
)
(displayedCandidates[index] as NSString).draw(
in: rctCandidatePhrase, withAttributes: activeCandidateAttr
)
accuWidth += currentWidth + 1.0
} }
} }
} }
@ -313,28 +309,24 @@ private class vwrCandidateUniversal: NSView {
} }
switch isVerticalLayout { switch isVerticalLayout {
case true: case true:
do { var accuHeight: CGFloat = 0.0
var accuHeight: CGFloat = 0.0 for (index, elementHeight) in elementHeights.enumerated() {
for (index, elementHeight) in elementHeights.enumerated() { let currentHeight = elementHeight
let currentHeight = elementHeight
if location.y >= accuHeight, location.y <= accuHeight + currentHeight { if location.y >= accuHeight, location.y <= accuHeight + currentHeight {
return index return index
}
accuHeight += currentHeight
} }
accuHeight += currentHeight
} }
case false: case false:
do { var accuWidth: CGFloat = 0.0
var accuWidth: CGFloat = 0.0 for (index, elementWidth) in elementWidths.enumerated() {
for (index, elementWidth) in elementWidths.enumerated() { let currentWidth = elementWidth
let currentWidth = elementWidth
if location.x >= accuWidth, location.x <= accuWidth + currentWidth { if location.x >= accuWidth, location.x <= accuWidth + currentWidth {
return index return index
}
accuWidth += currentWidth + 1.0
} }
accuWidth += currentWidth + 1.0
} }
} }
return NSNotFound return NSNotFound
@ -559,7 +551,7 @@ extension ctlCandidateUniversal {
} }
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
var candidates = [String]() var candidates = [(String, String)]()
let count = delegate.candidateCountForController(self) let count = delegate.candidateCountForController(self)
let keyLabelCount = keyLabels.count let keyLabelCount = keyLabels.count
@ -569,7 +561,7 @@ extension ctlCandidateUniversal {
candidates.append(candidate) candidates.append(candidate)
} }
candidateView.set( candidateView.set(
keyLabels: keyLabels.map(\.displayedText), displayedCandidates: candidates keyLabels: keyLabels.map(\.displayedText), displayedCandidates: candidates.map(\.1)
) )
var newSize = candidateView.sizeForView var newSize = candidateView.sizeForView
var frameRect = candidateView.frame var frameRect = candidateView.frame

View File

@ -158,7 +158,7 @@ struct suiPrefPaneGeneral: View {
} }
} }
Toggle( Toggle(
LocalizedStringKey("Show Hanyu-Pinyin in the inline composition buffer"), LocalizedStringKey("Show Hanyu-Pinyin in the inline composition buffer & tooltip"),
isOn: $selShowHanyuPinyinInCompositionBuffer isOn: $selShowHanyuPinyinInCompositionBuffer
).onChange(of: selShowHanyuPinyinInCompositionBuffer) { value in ).onChange(of: selShowHanyuPinyinInCompositionBuffer) { value in
mgrPrefs.showHanyuPinyinInCompositionBuffer = value mgrPrefs.showHanyuPinyinInCompositionBuffer = value

View File

@ -269,7 +269,7 @@
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vwf-Kq-s8M" userLabel="chkShowHanyuPinyinInCompositionBuffer"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vwf-Kq-s8M" userLabel="chkShowHanyuPinyinInCompositionBuffer">
<rect key="frame" x="19" y="42.5" width="406" height="16"/> <rect key="frame" x="19" y="42.5" width="406" height="16"/>
<buttonCell key="cell" type="check" title="Show Hanyu-Pinyin in the inline composition buffer" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="wFR-zX-M8H"> <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"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="cellTitle"/> <font key="font" metaFont="cellTitle"/>
</buttonCell> </buttonCell>

View File

@ -74,7 +74,7 @@
"ueU-Rz-a1C.title" = "Choose the behavior of (Shift+)Tab key in the candidate window."; "ueU-Rz-a1C.title" = "Choose the behavior of (Shift+)Tab key in the candidate window.";
"Uyz-xL-TVN.title" = "Output Settings"; "Uyz-xL-TVN.title" = "Output Settings";
"W24-T4-cg0.title" = "Enable CNS11643 Support (2022-06-15)"; "W24-T4-cg0.title" = "Enable CNS11643 Support (2022-06-15)";
"wFR-zX-M8H.title" = "Show Hanyu-Pinyin in the inline composition buffer"; "wFR-zX-M8H.title" = "Show Hanyu-Pinyin in the inline composition buffer & tooltip";
"wN3-k3-b2a.title" = "Choose your desired user data folder path. Will be omitted if invalid."; "wN3-k3-b2a.title" = "Choose your desired user data folder path. Will be omitted if invalid.";
"wQ9-px-b07.title" = "Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional."; "wQ9-px-b07.title" = "Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional.";
"Wvt-HE-LOv.title" = "Keyboard Layout"; "Wvt-HE-LOv.title" = "Keyboard Layout";

View File

@ -74,7 +74,7 @@
"ueU-Rz-a1C.title" = "入力候補陳列での (Shift+)Tab キーの輪番切替対象をご指定ください。"; "ueU-Rz-a1C.title" = "入力候補陳列での (Shift+)Tab キーの輪番切替対象をご指定ください。";
"Uyz-xL-TVN.title" = "出力設定"; "Uyz-xL-TVN.title" = "出力設定";
"W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-06-15)"; "W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-06-15)";
"wFR-zX-M8H.title" = "弁音合併入力(入力緩衝列で代わりに漢語弁音の音読み"; "wFR-zX-M8H.title" = "弁音合併入力(入力緩衝列とヒントで音読みを漢語弁音に";
"wN3-k3-b2a.title" = "欲しがるユーザー辞書保存先をご指定ください。無効の保存先設定は効かぬ。"; "wN3-k3-b2a.title" = "欲しがるユーザー辞書保存先をご指定ください。無効の保存先設定は効かぬ。";
"wQ9-px-b07.title" = "Apple 動態注音キーボード (大千と倚天伝統) を使うには、共通語分析器の注音配列を大千と設定すべきである。"; "wQ9-px-b07.title" = "Apple 動態注音キーボード (大千と倚天伝統) を使うには、共通語分析器の注音配列を大千と設定すべきである。";
"Wvt-HE-LOv.title" = "キーボード"; "Wvt-HE-LOv.title" = "キーボード";

View File

@ -74,7 +74,7 @@
"ueU-Rz-a1C.title" = "指定 (Shift+)Tab 热键在选字窗内的轮替操作对象。"; "ueU-Rz-a1C.title" = "指定 (Shift+)Tab 热键在选字窗内的轮替操作对象。";
"Uyz-xL-TVN.title" = "输出设定"; "Uyz-xL-TVN.title" = "输出设定";
"W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-06-15)"; "W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-06-15)";
"wFR-zX-M8H.title" = "拼音并击模式(组字区内看到的是汉语拼音)"; "wFR-zX-M8H.title" = "拼音并击(组字区与工具提示内显示汉语拼音)";
"wN3-k3-b2a.title" = "请在此指定您想指定的使用者语汇档案目录。无效值会被忽略。"; "wN3-k3-b2a.title" = "请在此指定您想指定的使用者语汇档案目录。无效值会被忽略。";
"wQ9-px-b07.title" = "Apple 动态注音键盘布局(大千与倚天)要求普通话/国音分析器的注音排列得配置为大千排列。"; "wQ9-px-b07.title" = "Apple 动态注音键盘布局(大千与倚天)要求普通话/国音分析器的注音排列得配置为大千排列。";
"Wvt-HE-LOv.title" = "键盘布局"; "Wvt-HE-LOv.title" = "键盘布局";

View File

@ -74,7 +74,7 @@
"ueU-Rz-a1C.title" = "指定 (Shift+)Tab 熱鍵在選字窗內的輪替操作對象。"; "ueU-Rz-a1C.title" = "指定 (Shift+)Tab 熱鍵在選字窗內的輪替操作對象。";
"Uyz-xL-TVN.title" = "輸出設定"; "Uyz-xL-TVN.title" = "輸出設定";
"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-06-15)"; "W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-06-15)";
"wFR-zX-M8H.title" = "拼音並擊模式(組字區內看到的是漢語拼音)"; "wFR-zX-M8H.title" = "拼音並擊(組字區與工具提示內顯示漢語拼音)";
"wN3-k3-b2a.title" = "請在此指定您想指定的使用者語彙檔案目錄。無效值會被忽略。"; "wN3-k3-b2a.title" = "請在此指定您想指定的使用者語彙檔案目錄。無效值會被忽略。";
"wQ9-px-b07.title" = "Apple 動態注音鍵盤佈局(大千與倚天)要求普通話/國音分析器的注音排列得配置為大千排列。"; "wQ9-px-b07.title" = "Apple 動態注音鍵盤佈局(大千與倚天)要求普通話/國音分析器的注音排列得配置為大千排列。";
"Wvt-HE-LOv.title" = "鍵盤佈局"; "Wvt-HE-LOv.title" = "鍵盤佈局";

View File

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

View File

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

View File

@ -1365,7 +1365,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
@ -1375,7 +1375,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests;
@ -1404,13 +1404,13 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests;
@ -1441,7 +1441,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
@ -1462,7 +1462,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
@ -1491,7 +1491,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1508,7 +1508,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
@ -1618,7 +1618,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -1649,7 +1649,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -1676,7 +1676,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -1701,7 +1701,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1723,7 +1723,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -1744,7 +1744,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -1766,7 +1766,7 @@
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1974; CURRENT_PROJECT_VERSION = 1980;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -1781,7 +1781,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.7.4; MARKETING_VERSION = 1.8.0;
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -365,7 +365,7 @@ class KeyHandlerTestsNormalCHS: XCTestCase {
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
XCTAssertTrue(state.candidates.contains("")) XCTAssertTrue(state.candidates.map(\.1).contains(""))
} }
mgrPrefs.halfWidthPunctuationEnabled = enabled mgrPrefs.halfWidthPunctuationEnabled = enabled
} }
@ -993,7 +993,7 @@ class KeyHandlerTestsNormalCHS: XCTestCase {
XCTAssertEqual(state.composingBuffer, "") XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 1) XCTAssertEqual(state.cursorIndex, 1)
let candidates = state.candidates let candidates = state.candidates
XCTAssertTrue(candidates.contains("")) XCTAssertTrue(candidates.map(\.1).contains(""))
} }
} }
@ -1030,7 +1030,7 @@ class KeyHandlerTestsNormalCHS: XCTestCase {
XCTAssertEqual(state.composingBuffer, "") XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 1) XCTAssertEqual(state.cursorIndex, 1)
let candidates = state.candidates let candidates = state.candidates
XCTAssertTrue(candidates.contains("")) XCTAssertTrue(candidates.map(\.1).contains(""))
} }
mgrPrefs.chooseCandidateUsingSpace = enabled mgrPrefs.chooseCandidateUsingSpace = enabled
} }

View File

@ -85,7 +85,7 @@ class KeyHandlerTestsSCPCCHT: XCTestCase {
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
XCTAssertTrue(state.candidates.contains("")) XCTAssertTrue(state.candidates.map(\.1).contains(""))
} }
} }
@ -238,7 +238,7 @@ class KeyHandlerTestsSCPCCHT: XCTestCase {
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
XCTAssertTrue(state.candidates.contains("")) XCTAssertTrue(state.candidates.map(\.1).contains(""))
} }
} }
@ -315,7 +315,7 @@ class KeyHandlerTestsSCPCCHT: XCTestCase {
print("\(state)") print("\(state)")
// XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)") // XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)")
// if let state = state as? InputState.AssociatedPhrases { // if let state = state as? InputState.AssociatedPhrases {
// XCTAssertTrue(state.candidates.contains("")) // XCTAssertTrue(state.candidates.map(\.1).contains(""))
// } // }
mgrPrefs.associatedPhrasesEnabled = enabled mgrPrefs.associatedPhrasesEnabled = enabled
} }