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

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

View File

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

View File

@ -56,7 +56,7 @@ class KeyHandler {
var compositor: Megrez.Compositor //
var currentLM: vChewing.LMInstantiator = .init() //
var currentUOM: vChewing.LMUserOverride = .init() //
var walkedAnchors: [Megrez.NodeAnchor] = [] //
var walkedAnchors: [Megrez.NodeAnchor] { compositor.walkedAnchors } //
/// (ctlInputMethod)便
var delegate: KeyHandlerDelegate?
@ -95,7 +95,6 @@ class KeyHandler {
func clear() {
composer.clear()
compositor.clear()
walkedAnchors.removeAll()
}
// MARK: - Functions dealing with Megrez.
@ -103,7 +102,7 @@ class KeyHandler {
/// Megrez 使便
///
/// 使 Node Crossing
var actualCandidateCursorIndex: Int {
var actualCandidateCursor: Int {
mgrPrefs.useRearCursorMode ? min(compositorCursorIndex, compositorLength - 1) : max(compositorCursorIndex, 1)
}
@ -113,11 +112,11 @@ class KeyHandler {
///
///
func walk() {
walkedAnchors = compositor.walk()
compositor.walk()
// GraphViz
if mgrPrefs.isDebugModeEnabled {
let result = compositor.grid.dumpDOT
let result = compositor.dumpDOT
do {
try result.write(
toFile: "/private/var/tmp/vChewing-visualization.dot",
@ -137,12 +136,10 @@ class KeyHandler {
///
var commitOverflownCompositionAndWalk: String {
var textToCommit = ""
if compositor.grid.width > mgrPrefs.composingBufferSize, !walkedAnchors.isEmpty {
if compositor.width > mgrPrefs.composingBufferSize, !walkedAnchors.isEmpty {
let anchor: Megrez.NodeAnchor = walkedAnchors[0]
if let theNode = anchor.node {
textToCommit = theNode.currentKeyValue.value
}
compositor.removeHeadReadings(count: anchor.spanningLength)
textToCommit = anchor.node.currentPair.value
compositor.removeHeadReadings(count: anchor.spanLength)
}
walk()
return textToCommit
@ -152,10 +149,10 @@ class KeyHandler {
/// - Parameter key:
/// - Returns:
/// nil
func buildAssociatePhraseArray(withKey key: String) -> [String] {
var arrResult: [String] = []
func buildAssociatePhraseArray(withKey key: String) -> [(String, String)] {
var arrResult: [(String, String)] = []
if currentLM.hasAssociatedPhrasesFor(key: key) {
arrResult.append(contentsOf: currentLM.associatedPhrasesFor(key: key))
arrResult = currentLM.associatedPhrasesFor(key: key).map { ("", $0) }
}
return arrResult
}
@ -165,35 +162,32 @@ class KeyHandler {
/// - Parameters:
/// - value:
/// - respectCursorPushing: true
func fixNode(value: String, respectCursorPushing: Bool = true) {
let cursorIndex = min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength)
func fixNode(candidate: (String, String), respectCursorPushing: Bool = true) {
let theCandidate: Megrez.KeyValuePaired = .init(key: candidate.0, value: candidate.1)
let adjustedCursor = max(0, min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength))
//
let selectedNode: Megrez.NodeAnchor = compositor.grid.fixNodeSelectedCandidate(
location: cursorIndex, value: value
)
let selectedNode: Megrez.NodeAnchor = compositor.fixNodeWithCandidate(theCandidate, at: adjustedCursor)
//
if !mgrPrefs.useSCPCTypingMode {
var addToUserOverrideModel = true
//
if selectedNode.spanningLength != value.count {
if selectedNode.spanLength != theCandidate.value.count {
IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
addToUserOverrideModel = false
}
if addToUserOverrideModel {
if let theNode = selectedNode.node {
// SymbolLM Score -12
if theNode.scoreFor(candidate: value) <= -12 {
IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
addToUserOverrideModel = false
}
// SymbolLM Score -12
if selectedNode.node.scoreForPaired(candidate: theCandidate) <= -12 {
IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
addToUserOverrideModel = false
}
}
if addToUserOverrideModel {
IME.prtDebugIntel("UOM: Start Observation.")
// trigram
// trigram
//
//
currentUOM.observe(
walkedAnchors: walkedAnchors, cursorIndex: cursorIndex, candidate: value,
walkedAnchors: walkedAnchors, cursorIndex: adjustedCursor, candidate: theCandidate.value,
timestamp: NSDate().timeIntervalSince1970
)
}
@ -204,70 +198,57 @@ class KeyHandler {
///
if mgrPrefs.moveCursorAfterSelectingCandidate, respectCursorPushing {
var nextPosition = 0
for theAnchor in walkedAnchors {
if nextPosition >= cursorIndex { break }
nextPosition += theAnchor.spanningLength
}
if nextPosition <= compositorLength {
compositorCursorIndex = nextPosition
}
compositor.jumpCursorBySpan(to: .front)
}
}
///
func markNodesFixedIfNecessary() {
let width = compositor.grid.width
let width = compositor.width
if width <= kMaxComposingBufferNeedsToWalkSize {
return
}
var index = 0
for anchor in walkedAnchors {
guard let node = anchor.node else { break }
if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
if node.score < node.kSelectedCandidateScore {
compositor.grid.fixNodeSelectedCandidate(
location: index + anchor.spanningLength, value: node.currentKeyValue.value
)
if anchor.node.score < Megrez.Node.kSelectedCandidateScore {
compositor.fixNodeWithCandidate(anchor.node.currentPair, at: index + anchor.spanLength)
}
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 arrCandidates: [String] = []
var arrCandidates: [Megrez.KeyValuePaired] = .init()
/// nodes
///
///
if arrAnchors.isEmpty { return arrCandidates }
if arrAnchors.isEmpty { return .init() }
//
arrAnchors = arrAnchors.stableSort { $0.keyLength > $1.keyLength }
//
for currentNodeAnchor in arrAnchors {
guard let currentNode = currentNodeAnchor.node else { continue }
for currentCandidate in currentNode.candidates {
// / JIS
//
//
arrCandidates.append(currentCandidate.value)
}
for currentCandidate in arrAnchors.map(\.node.candidates).joined() {
// / JIS
//
//
arrCandidates.append(.init(key: currentCandidate.key, value: currentCandidate.value))
}
// 調
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 arrSuggestedCandidates: [String] = arrSuggestedUnigrams.map(\.keyValue.value)
let arrSuggestedCandidates: [Megrez.KeyValuePaired] = arrSuggestedUnigrams.map(\.keyValue)
arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates
arrCandidates = arrCandidates.deduplicate
arrCandidates = arrCandidates.stableSort { $0.count > $1.count }
return arrCandidates
arrCandidates = arrCandidates.stableSort { $0.key.split(separator: "-").count > $1.key.split(separator: "-").count }
return arrCandidates.map { ($0.key, $0.value) }
}
///
@ -284,17 +265,17 @@ class KeyHandler {
if mgrPrefs.useSCPCTypingMode { return }
///
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel { return }
/// trigram
///
let overrideValue = fetchSuggestedCandidates().first?.keyValue.value ?? ""
///
if !overrideValue.isEmpty {
IME.prtDebugIntel(
"UOM: Suggestion retrieved, overriding the node score of the selected candidate.")
compositor.grid.overrideNodeScoreForSelectedCandidate(
location: min(actualCandidateCursorIndex + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength),
compositor.overrideNodeScoreForSelectedCandidate(
location: min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength),
value: overrideValue,
overridingScore: findHighestScore(nodes: rawAnchorsOfNodes, epsilon: kEpsilon)
overridingScore: findHighestScore(nodeAnchors: rawAnchorsOfNodes, epsilon: kEpsilon)
)
} else {
IME.prtDebugIntel("UOM: Blank suggestion retrieved, dismissing.")
@ -306,14 +287,8 @@ class KeyHandler {
/// - nodes:
/// - epsilon:
/// - Returns:
func findHighestScore(nodes: [Megrez.NodeAnchor], epsilon: Double) -> Double {
var highestScore: Double = 0
for currentAnchor in nodes {
if let theNode = currentAnchor.node {
highestScore = max(theNode.highestUnigramScore, highestScore)
}
}
return highestScore + epsilon
func findHighestScore(nodeAnchors: [Megrez.NodeAnchor], epsilon: Double) -> Double {
return nodeAnchors.map(\.node.highestUnigramScore).max() ?? 0 + epsilon
}
// MARK: - Extracted methods and functions (Tekkon).
@ -359,41 +334,6 @@ class KeyHandler {
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).
///
@ -404,8 +344,8 @@ class KeyHandler {
/// 使 nodesCrossing macOS
/// nodeCrossing
mgrPrefs.useRearCursorMode
? compositor.grid.nodesBeginningAt(location: actualCandidateCursorIndex)
: compositor.grid.nodesEndingAt(location: actualCandidateCursorIndex)
? compositor.nodesBeginningAt(location: actualCandidateCursor)
: compositor.nodesEndingAt(location: actualCandidateCursor)
}
///
@ -431,13 +371,13 @@ class KeyHandler {
///
func insertToCompositorAtCursor(reading: String) {
compositor.insertReadingAtCursor(reading: reading)
compositor.insertReading(reading)
}
///
var compositorCursorIndex: Int {
get { compositor.cursorIndex }
set { compositor.cursorIndex = newValue }
get { compositor.cursor }
set { compositor.cursor = newValue }
}
///
@ -449,13 +389,43 @@ class KeyHandler {
///
/// Rear
func deleteCompositorReadingAtTheRearOfCursor() {
compositor.deleteReadingAtTheRearOfCursor()
compositor.dropReading(direction: .rear)
}
///
///
/// Front
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 {
switch ctlCandidateCurrent.currentLayout {
case .horizontal:
do {
if !ctlCandidateCurrent.highlightPreviousCandidate() {
IME.prtDebugIntel("1145148D")
errorCallback()
}
if !ctlCandidateCurrent.highlightPreviousCandidate() {
IME.prtDebugIntel("1145148D")
errorCallback()
}
case .vertical:
do {
if !ctlCandidateCurrent.showPreviousPage() {
IME.prtDebugIntel("1919810D")
errorCallback()
}
if !ctlCandidateCurrent.showPreviousPage() {
IME.prtDebugIntel("1919810D")
errorCallback()
}
}
return true
@ -187,18 +183,14 @@ extension KeyHandler {
if input.isRight {
switch ctlCandidateCurrent.currentLayout {
case .horizontal:
do {
if !ctlCandidateCurrent.highlightNextCandidate() {
IME.prtDebugIntel("9B65138D")
errorCallback()
}
if !ctlCandidateCurrent.highlightNextCandidate() {
IME.prtDebugIntel("9B65138D")
errorCallback()
}
case .vertical:
do {
if !ctlCandidateCurrent.showNextPage() {
IME.prtDebugIntel("9244908D")
errorCallback()
}
if !ctlCandidateCurrent.showNextPage() {
IME.prtDebugIntel("9244908D")
errorCallback()
}
}
return true
@ -220,18 +212,14 @@ extension KeyHandler {
if input.isUp {
switch ctlCandidateCurrent.currentLayout {
case .horizontal:
do {
if !ctlCandidateCurrent.showPreviousPage() {
IME.prtDebugIntel("9B614524")
errorCallback()
}
if !ctlCandidateCurrent.showPreviousPage() {
IME.prtDebugIntel("9B614524")
errorCallback()
}
case .vertical:
do {
if !ctlCandidateCurrent.highlightPreviousCandidate() {
IME.prtDebugIntel("ASD9908D")
errorCallback()
}
if !ctlCandidateCurrent.highlightPreviousCandidate() {
IME.prtDebugIntel("ASD9908D")
errorCallback()
}
}
return true
@ -242,18 +230,14 @@ extension KeyHandler {
if input.isDown {
switch ctlCandidateCurrent.currentLayout {
case .horizontal:
do {
if !ctlCandidateCurrent.showNextPage() {
IME.prtDebugIntel("92B990DD")
errorCallback()
}
if !ctlCandidateCurrent.showNextPage() {
IME.prtDebugIntel("92B990DD")
errorCallback()
}
case .vertical:
do {
if !ctlCandidateCurrent.highlightNextCandidate() {
IME.prtDebugIntel("6B99908D")
errorCallback()
}
if !ctlCandidateCurrent.highlightNextCandidate() {
IME.prtDebugIntel("6B99908D")
errorCallback()
}
}
return true
@ -274,7 +258,7 @@ extension KeyHandler {
// MARK: End Key
var candidates: [String]!
var candidates: [(String, String)]!
if let state = state as? InputState.ChoosingCandidate {
candidates = state.candidates
@ -334,22 +318,8 @@ extension KeyHandler {
/// - /
/// -
var punctuationNamePrefix = ""
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 punctuationNamePrefix: String = generatePunctuationNamePrefix(withKeyCondition: input)
let parser = currentMandarinParser
let arrCustomPunctuations: [String] = [
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),
]

View File

@ -225,7 +225,7 @@ extension KeyHandler {
)
if choosingCandidates.candidates.count == 1 {
clear()
let text: String = choosingCandidates.candidates.first ?? ""
let text: String = choosingCandidates.candidates.first?.1 ?? ""
stateCallback(InputState.Committing(textToCommit: text))
if !mgrPrefs.associatedPhrasesEnabled {
@ -414,20 +414,7 @@ extension KeyHandler {
/// - /
/// -
var punctuationNamePrefix = ""
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 punctuationNamePrefix: String = generatePunctuationNamePrefix(withKeyCondition: input)
let parser = currentMandarinParser
let arrCustomPunctuations: [String] = [
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),

View File

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

View File

@ -236,18 +236,11 @@ public struct Tekkon {
public func getComposition(isHanyuPinyin: Bool = false, isTextBookStyle: Bool = false) -> String {
switch isHanyuPinyin {
case false: //
var valReturnZhuyin = value.replacingOccurrences(of: " ", with: "")
if isTextBookStyle, valReturnZhuyin.contains("˙") {
valReturnZhuyin = String(valReturnZhuyin.dropLast())
valReturnZhuyin.insert("˙", at: valReturnZhuyin.startIndex)
}
return valReturnZhuyin
let valReturnZhuyin = value.replacingOccurrences(of: " ", with: "")
return isTextBookStyle ? cnvZhuyinChainToTextbookReading(target: valReturnZhuyin) : valReturnZhuyin
case true: //
var valReturnPinyin = Tekkon.cnvPhonaToHanyuPinyin(target: value)
if isTextBookStyle {
valReturnPinyin = Tekkon.cnvHanyuPinyinToTextbookStyle(target: valReturnPinyin)
}
return valReturnPinyin
let valReturnPinyin = Tekkon.cnvPhonaToHanyuPinyin(target: value)
return isTextBookStyle ? Tekkon.cnvHanyuPinyinToTextbookStyle(target: valReturnPinyin) : valReturnPinyin
}
}
@ -852,6 +845,41 @@ public struct Tekkon {
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 = [ //
[" ", "1"], ["ˊ", "2"], ["ˇ", "3"], ["ˋ", "4"], ["˙", "5"],

View File

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

View File

@ -55,7 +55,7 @@ extension ctlInputMethod {
return false
}
var isCandidateWindowVertical: Bool {
var candidates: [String] = []
var candidates: [(String, String)] = .init()
if let state = state as? InputState.ChoosingCandidate {
candidates = state.candidates
} else if let state = state as? InputState.AssociatedPhrases {
@ -63,13 +63,11 @@ extension ctlInputMethod {
}
if isTypingVertical { return true }
// 使
candidates.sort {
$0.count > $1.count
}
//
// 使
// Beer emoji
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))
// true
}

View File

@ -143,6 +143,10 @@ extension ctlInputMethod {
withTitle: NSLocalizedString("Optimize Memorized Phrases", comment: ""),
action: #selector(removeUnigramsFromUOM(_:)), keyEquivalent: ""
)
menu.addItem(
withTitle: NSLocalizedString("Clear Memorized Phrases", comment: ""),
action: #selector(clearUOM(_:)), keyEquivalent: ""
)
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?) {
(NSApp.delegate as? AppDelegate)?.showAbout()
NSApp.activate(ignoringOtherApps: true)

View File

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

View File

@ -130,13 +130,12 @@ extension vChewing {
func convertKeyFrom(
walkedAnchors: [Megrez.NodeAnchor], cursorIndex: Int, readingOnly: Bool = false
) -> String {
let arrEndingPunctuation = ["", "", "", "", "", "", "", ""]
let whiteList = "你他妳她祢衪它牠再在"
var arrNodes: [Megrez.NodeAnchor] = []
var intLength = 0
for theNodeAnchor in walkedAnchors {
arrNodes.append(theNodeAnchor)
intLength += theNodeAnchor.spanningLength
intLength += theNodeAnchor.spanLength
if intLength >= cursorIndex {
break
}
@ -146,9 +145,8 @@ extension vChewing {
arrNodes = Array(arrNodes.reversed())
guard let kvCurrent = arrNodes[0].node?.currentKeyValue,
!arrEndingPunctuation.contains(kvCurrent.value)
else {
let kvCurrent = arrNodes[0].node.currentPair
guard !kvCurrent.key.contains("_") else {
return ""
}
@ -173,20 +171,18 @@ extension vChewing {
}
if arrNodes.count >= 2,
let kvPreviousThisOne = arrNodes[1].node?.currentKeyValue,
!arrEndingPunctuation.contains(kvPrevious.value),
!kvPrevious.key.contains("_"),
kvPrevious.key.split(separator: "-").count == kvPrevious.value.count
{
kvPrevious = kvPreviousThisOne
kvPrevious = arrNodes[1].node.currentPair
readingStack = kvPrevious.key + readingStack
}
if arrNodes.count >= 3,
let kvAnteriorThisOne = arrNodes[2].node?.currentKeyValue,
!arrEndingPunctuation.contains(kvAnterior.value),
!kvAnterior.key.contains("_"),
kvAnterior.key.split(separator: "-").count == kvAnterior.value.count
{
kvAnterior = kvAnteriorThisOne
kvAnterior = arrNodes[2].node.currentPair
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) {
let encoder = JSONEncoder()
do {

View File

@ -224,39 +224,60 @@ enum mgrLangModel {
// Swift appendingPathComponent URL .path
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userPhrasesDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "userdata-cht.txt" : "userdata-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
}
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userSymbolDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "usersymbolphrases-cht.txt" : "usersymbolphrases-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
}
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userAssociatesDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "associatedPhrases-cht.txt" : "associatedPhrases-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
}
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userFilteredDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "exclude-phrases-cht.txt" : "exclude-phrases-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
}
/// 使
/// - Parameter mode:
/// - Returns: URL
static func userReplacementsDataURL(_ mode: InputMode) -> URL {
let fileName = (mode == InputMode.imeModeCHT) ? "phrases-replacement-cht.txt" : "phrases-replacement-chs.txt"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
}
/// 使
/// - Returns: URL
static func userSymbolNodeDataURL() -> URL {
let fileName = "symbols.dat"
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
}
/// 使使
/// ~/Library/Application Support/vChewing/使
/// - Parameter mode:
/// - Returns: URL
static func userOverrideModelDataURL(_ mode: InputMode) -> URL {
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: - 使
@ -463,4 +484,15 @@ enum mgrLangModel {
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 {
///
public class Compositor {
public class Compositor: Grid {
///
public enum TypingDirection { case front, rear }
///
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 var mutGrid: Grid = .init()
private(set) var readings: [String] = []
/// 使
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 cursorIndex: Int {
get { mutCursorIndex }
set { mutCursorIndex = (newValue < 0) ? 0 : min(newValue, mutReadings.count) }
}
public var joinSeparator: String = "-"
///
public var isEmpty: Bool { mutGrid.isEmpty }
///
public var grid: Grid { mutGrid }
///
public var length: Int { mutReadings.count }
///
public var readings: [String] { mutReadings }
public var length: Int { readings.count }
///
/// - 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:
/// - lm: Megrez.LanguageModel
/// - lm: Megrez.LangModel
/// - length: 10
/// - separator:
public init(lm: LanguageModelProtocol, length: Int = 10, separator: String = "") {
mutLM = lm
mutGrid = .init(spanLength: abs(length)) //
public init(lm: LangModelProtocol, length: Int = 10, separator: String = "-") {
langModel = lm
super.init(spanLength: abs(length)) //
joinSeparator = separator
}
///
public func clear() {
mutCursorIndex = 0
mutReadings.removeAll()
mutGrid.clear()
override public func clear() {
super.clear()
cursor = 0
readings.removeAll()
walkedAnchors.removeAll()
}
///
/// - Parameters:
/// - reading:
public func insertReadingAtCursor(reading: String) {
mutReadings.insert(reading, at: mutCursorIndex)
mutGrid.expandGridByOneAt(location: mutCursorIndex)
build()
mutCursorIndex += 1
}
///
/// Rear
@discardableResult public func deleteReadingAtTheRearOfCursor() -> Bool {
if mutCursorIndex == 0 {
return false
}
mutReadings.remove(at: mutCursorIndex - 1)
mutCursorIndex -= 1
mutGrid.shrinkGridByOneAt(location: mutCursorIndex)
@discardableResult public func insertReading(_ reading: String) -> Bool {
guard !reading.isEmpty, langModel.hasUnigramsFor(key: reading) else { return false }
readings.insert(reading, at: cursor)
resizeGridByOneAt(location: cursor, to: .expand)
build()
cursor += 1
return true
}
///
/// Front
@discardableResult public func deleteReadingToTheFrontOfCursor() -> Bool {
if mutCursorIndex == mutReadings.count {
///
///
/// RearFront
/// - Parameter direction:
/// - Returns:
@discardableResult public func dropReading(direction: TypingDirection) -> Bool {
let isBackSpace = direction == .rear
if cursor == (isBackSpace ? 0 : readings.count) {
return false
}
mutReadings.remove(at: mutCursorIndex)
mutGrid.shrinkGridByOneAt(location: mutCursorIndex)
readings.remove(at: cursor - (isBackSpace ? 1 : 0))
cursor -= (isBackSpace ? 1 : 0)
resizeGridByOneAt(location: cursor, to: .shrink)
build()
return true
}
@ -118,98 +135,84 @@ extension Megrez {
///
@discardableResult public func removeHeadReadings(count: Int) -> Bool {
let count = abs(count) //
if count > length {
return false
}
if count > length { return false }
for _ in 0..<count {
if mutCursorIndex > 0 {
mutCursorIndex -= 1
}
if !mutReadings.isEmpty {
mutReadings.removeFirst()
mutGrid.shrinkGridByOneAt(location: 0)
cursor = max(cursor - 1, 0)
if !readings.isEmpty {
readings.removeFirst()
resizeGridByOneAt(location: 0, to: .shrink)
}
build()
}
return true
}
// MARK: - Walker
///
/// - Parameters:
/// - location:
/// - accumulatedScore: 0
/// - joinedPhrase: 使
/// - longPhrases: 使
public func walk(
at location: Int = 0,
score accumulatedScore: Double = 0.0,
joinedPhrase: String = "",
longPhrases: [String] = .init()
) -> [NodeAnchor] {
let newLocation = (mutGrid.width) - abs(location) //
return Array(
reverseWalk(
at: newLocation, score: accumulatedScore,
joinedPhrase: joinedPhrase, longPhrases: longPhrases
).reversed())
/// - Returns:
@discardableResult public func walk() -> [NodeAnchor] {
let newLocation = width
//
walkedAnchors = Array(
reverseWalk(at: newLocation).reversed()
).lazy.filter { !$0.isEmpty }
updateCursorJumpingTables(walkedAnchors)
return walkedAnchors
}
///
// MARK: - Private functions
///
/// - Parameters:
/// - location:
/// - accumulatedScore: 0
/// - mass: 0
/// - joinedPhrase: 使
/// - longPhrases: 使
public func reverseWalk(
/// - Returns:
private func reverseWalk(
at location: Int,
score accumulatedScore: Double = 0.0,
mass: Double = 0.0,
joinedPhrase: String = "",
longPhrases: [String] = .init()
) -> [NodeAnchor] {
let location = abs(location) //
if location == 0 || location > mutGrid.width {
if location == 0 || location > width {
return .init()
}
var paths = [[NodeAnchor]]()
var nodes = mutGrid.nodesEndingAt(location: location)
nodes = nodes.stableSorted {
let nodes = nodesEndingAt(location: location).stableSorted {
$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]
anchorZero.accumulatedScore = accumulatedScore + nodeZero.score
var theAnchor = nodes[0]
theAnchor.mass = mass + nodes[0].node.score
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)
} else if !longPhrases.isEmpty {
var path = [NodeAnchor]()
for theAnchor in nodes {
guard let theNode = theAnchor.node else { continue }
var theAnchor = theAnchor
let joinedValue = theNode.currentKeyValue.value + joinedPhrase
let joinedValue = theAnchor.node.currentPair.value + joinedPhrase
//
// /////////使
//
if longPhrases.contains(joinedValue) {
theAnchor.accumulatedScore = kDroppedPathScore
theAnchor.mass = kDroppedPathScore
path.insert(theAnchor, at: 0)
paths.append(path)
continue
}
theAnchor.accumulatedScore = accumulatedScore + theNode.score
theAnchor.mass = mass + theAnchor.node.score
path = reverseWalk(
at: location - theAnchor.spanningLength,
score: theAnchor.accumulatedScore,
at: location - theAnchor.spanLength,
mass: theAnchor.mass,
joinedPhrase: (joinedValue.count >= longPhrases[0].count) ? "" : joinedValue,
longPhrases: .init()
)
@ -219,9 +222,8 @@ extension Megrez {
} else {
//
var longPhrases = [String]()
for theAnchor in nodes.lazy.filter({ $0.spanningLength > 1 }) {
guard let theNode = theAnchor.node else { continue }
longPhrases.append(theNode.currentKeyValue.value)
for theAnchor in nodes.lazy.filter({ $0.spanLength > 1 }) {
longPhrases.append(theAnchor.node.currentPair.value)
}
longPhrases = longPhrases.stableSorted {
@ -229,12 +231,11 @@ extension Megrez {
}
for theAnchor in nodes {
var theAnchor = theAnchor
guard let theNode = theAnchor.node else { continue }
theAnchor.accumulatedScore = accumulatedScore + theNode.score
theAnchor.mass = mass + theAnchor.node.score
var path = [NodeAnchor]()
path = reverseWalk(
at: location - theAnchor.spanningLength, score: theAnchor.accumulatedScore,
joinedPhrase: (theAnchor.spanningLength > 1) ? "" : theNode.currentKeyValue.value,
at: location - theAnchor.spanLength, mass: theAnchor.mass,
joinedPhrase: (theAnchor.spanLength > 1) ? "" : theAnchor.node.currentPair.value,
longPhrases: .init()
)
path.insert(theAnchor, at: 0)
@ -248,31 +249,29 @@ extension Megrez {
var result: [NodeAnchor] = paths[0]
for neta in paths.lazy.filter({
$0.last!.accumulatedScore > result.last!.accumulatedScore
$0.last!.mass > result.last!.mass
}) {
result = neta
}
return result
return result // walk()
}
// MARK: - Private functions
private func build() {
let itrBegin: Int =
(mutCursorIndex < maxBuildSpanLength) ? 0 : mutCursorIndex - maxBuildSpanLength
let itrEnd: Int = min(mutCursorIndex + maxBuildSpanLength, mutReadings.count)
(cursor < maxBuildSpanLength) ? 0 : cursor - maxBuildSpanLength
let itrEnd: Int = min(cursor + maxBuildSpanLength, readings.count)
for p in itrBegin..<itrEnd {
for q in 1..<maxBuildSpanLength {
if p + q > itrEnd { break }
let arrSlice = mutReadings[p..<(p + q)]
let arrSlice = readings[p..<(p + q)]
let combinedReading: String = join(slice: arrSlice, separator: joinSeparator)
if mutGrid.hasMatchedNode(location: p, spanningLength: q, key: combinedReading) { continue }
let unigrams: [Unigram] = mutLM.unigramsFor(key: combinedReading)
if hasMatchedNode(location: p, spanLength: q, key: combinedReading) { continue }
let unigrams: [Unigram] = langModel.unigramsFor(key: combinedReading)
if unigrams.isEmpty { continue }
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 {
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 {
///
///
public class Grid {
///
public enum ResizeBehavior { case expand, shrink }
///
private var mutSpans: [Megrez.Span]
private(set) var spans: [Megrez.SpanUnit]
///
private var mutMaxBuildSpanLength = 10
///
public var maxBuildSpanLength: Int { mutMaxBuildSpanLength }
///
private(set) var maxBuildSpanLength = 10
///
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) {
mutMaxBuildSpanLength = spanLength
mutSpans = [Megrez.Span]()
maxBuildSpanLength = spanLength
spans = [Megrez.SpanUnit]()
}
///
public func clear() {
mutSpans.removeAll()
spans.removeAll()
}
///
/// - Parameters:
/// - node:
/// - location:
/// - spanningLength:
public func insertNode(node: Node, location: Int, spanningLength: Int) {
/// - spanLength:
public func insertNode(node: Node, location: Int, spanLength: Int) {
let location = abs(location) //
let spanningLength = abs(spanningLength) //
if location >= mutSpans.count {
let diff = location - mutSpans.count + 1
let spanLength = abs(spanLength) //
if location >= spans.count {
let diff = location - spans.count + 1
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:
/// - location:
/// - spanningLength:
/// - spanLength:
/// - 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 spanningLength = abs(spanningLength) //
if location > mutSpans.count {
let spanLength = abs(spanLength) //
if location > spans.count {
return false
}
let n = mutSpans[location].node(length: spanningLength)
let n = spans[location].nodeOf(length: spanLength)
return n != nil && key == n?.key
}
///
///
/// - Parameters:
/// - location:
public func expandGridByOneAt(location: Int) {
let location = abs(location) //
mutSpans.insert(Span(), at: location)
if location == 0 || location == mutSpans.count { return }
public func resizeGridByOneAt(location: Int, to behavior: ResizeBehavior) {
let location = max(0, min(width, location)) //
switch behavior {
case .expand:
spans.insert(SpanUnit(), at: location)
if [spans.count, 0].contains(location) { return }
case .shrink:
if location >= spans.count { return }
spans.remove(at: location)
}
for i in 0..<location {
// zaps overlapping spans
mutSpans[i].removeNodeOfLengthGreaterThan(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)
spans[i].dropNodesBeyond(length: location - i)
}
}
@ -120,21 +109,21 @@ extension Megrez {
public func nodesBeginningAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if location >= mutSpans.count { return results }
// mutSpans location 0
let span = mutSpans[location]
if location >= spans.count { return results }
// spans location 0
let span = spans[location]
for i in 1...maxBuildSpanLength {
if let np = span.node(length: i) {
if let np = span.nodeOf(length: i) {
results.append(
.init(
node: np,
location: location,
spanningLength: i
spanLength: i
)
)
}
}
return results
return results //
}
///
@ -143,21 +132,21 @@ extension Megrez {
public func nodesEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if mutSpans.isEmpty || location > mutSpans.count { return results }
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
let span = mutSpans[i]
if i + span.maximumLength < location { continue }
if let np = span.node(length: location - i) {
let span = spans[i]
if i + span.maxLength < location { continue }
if let np = span.nodeOf(length: location - i) {
results.append(
.init(
node: np,
location: i,
spanningLength: location - i
spanLength: location - i
)
)
}
}
return results
return results //
}
///
@ -166,46 +155,76 @@ extension Megrez {
public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] {
let location = abs(location) //
var results = [NodeAnchor]()
if mutSpans.isEmpty || location > mutSpans.count { return results }
if spans.isEmpty || location > spans.count { return results }
for i in 0..<location {
let span = mutSpans[i]
if i + span.maximumLength < location { continue }
for j in 1...span.maximumLength {
let span = spans[i]
if i + span.maxLength < location { continue }
for j in 1...span.maxLength {
if i + j < location { continue }
if let np = span.node(length: j) {
if let np = span.nodeOf(length: j) {
results.append(
.init(
node: np,
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:
/// - location:
/// - value:
@discardableResult public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor {
/// - value:
@discardableResult public func fixNodeWithCandidate(_ pair: KeyValuePaired, at location: Int) -> NodeAnchor {
let location = abs(location) //
var node = NodeAnchor()
for nodeAnchor in nodesCrossingOrEndingAt(location: location) {
guard let theNode = nodeAnchor.node else {
continue
}
let candidates = theNode.candidates
for theAnchor in nodesOverlappedAt(location: location) {
let candidates = theAnchor.node.candidates
//
theNode.resetCandidate()
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate.value == value {
theNode.selectCandidateAt(index: i)
node = nodeAnchor
if candidate == pair {
theAnchor.node.selectCandidateAt(index: i)
node = theAnchor
break
}
}
@ -220,16 +239,13 @@ extension Megrez {
/// - overridingScore:
public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) {
let location = abs(location) //
for nodeAnchor in nodesCrossingOrEndingAt(location: location) {
guard let theNode = nodeAnchor.node else {
continue
}
let candidates = theNode.candidates
for theAnchor in nodesOverlappedAt(location: location) {
let candidates = theAnchor.node.candidates
//
theNode.resetCandidate()
theAnchor.node.resetCandidate()
for (i, candidate) in candidates.enumerated() {
if candidate.value == value {
theNode.selectFloatingCandidateAt(index: i, score: overridingScore)
theAnchor.node.selectFloatingCandidateAt(index: i, score: overridingScore)
break
}
}
@ -244,29 +260,22 @@ extension Megrez.Grid {
/// GraphViz
public var dumpDOT: String {
var strOutput = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
for (p, span) in mutSpans.enumerated() {
for ni in 0...(span.maximumLength) {
guard let np: Megrez.Node = span.node(length: ni) else {
continue
}
for (p, span) in spans.enumerated() {
for ni in 0...(span.maxLength) {
guard let np = span.nodeOf(length: ni) else { continue }
if p == 0 {
strOutput += "BOS -> \(np.currentKeyValue.value);\n"
strOutput += "BOS -> \(np.currentPair.value);\n"
}
strOutput += "\(np.currentKeyValue.value);\n"
if (p + ni) < mutSpans.count {
let destinationSpan = mutSpans[p + ni]
for q in 0...(destinationSpan.maximumLength) {
if let dn = destinationSpan.node(length: q) {
strOutput += np.currentKeyValue.value + " -> " + dn.currentKeyValue.value + ";\n"
}
strOutput += "\(np.currentPair.value);\n"
if (p + ni) < spans.count {
let destinationSpan = spans[p + ni]
for q in 0...(destinationSpan.maxLength) {
guard let dn = destinationSpan.nodeOf(length: q) else { continue }
strOutput += np.currentPair.value + " -> " + dn.currentPair.value + ";\n"
}
}
if (p + ni) == mutSpans.count {
strOutput += np.currentKeyValue.value + " -> EOS;\n"
}
guard (p + ni) == spans.count else { continue }
strOutput += np.currentPair.value + " -> EOS;\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 {
///
@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 spanningLength: Int = 0
///
public var spanLength: Int = 0
///
public var accumulatedScore: Double = 0.0
public var mass: Double = 0.0
///
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 {
var stream = ""
stream += "{@(" + String(location) + "," + String(spanningLength) + "),"
if let node = node {
stream += "{@(" + String(location) + "," + String(spanLength) + "),"
if node.key.isEmpty {
stream += node.description
} else {
stream += "null"
@ -54,12 +63,12 @@ extension Megrez {
///
public var scoreForSort: Double {
node?.score ?? 0
isEmpty ? node.score : 0
}
}
}
// MARK: - DumpDOT-related functions.
// MARK: - Array Extensions.
extension Array where Element == Megrez.NodeAnchor {
///
@ -70,4 +79,14 @@ extension Array where Element == Megrez.NodeAnchor {
}
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 {
///
@frozen public struct Span {
@frozen public struct SpanUnit {
///
private var mutLengthNodeMap: [Int: Megrez.Node] = [:]
///
private var mutMaximumLength: Int = 0
///
public var maximumLength: Int {
mutMaximumLength
}
private var lengthNodeMap: [Int: Megrez.Node] = [:]
///
private(set) var maxLength: Int = 0
///
mutating func clear() {
mutLengthNodeMap.removeAll()
mutMaximumLength = 0
lengthNodeMap.removeAll()
maxLength = 0
}
///
@ -48,37 +43,37 @@ extension Megrez {
/// - length:
mutating func insert(node: Node, length: Int) {
let length = abs(length) //
mutLengthNodeMap[length] = node
mutMaximumLength = max(mutMaximumLength, length)
lengthNodeMap[length] = node
maxLength = max(maxLength, length)
}
///
/// - Parameters:
/// - length:
mutating func removeNodeOfLengthGreaterThan(_ length: Int) {
mutating func dropNodesBeyond(length: Int) {
let length = abs(length) //
if length > mutMaximumLength { return }
if length > maxLength { return }
var lenMax = 0
var removalList: [Int: Megrez.Node] = [:]
for key in mutLengthNodeMap.keys {
for key in lengthNodeMap.keys {
if key > length {
removalList[key] = mutLengthNodeMap[key]
removalList[key] = lengthNodeMap[key]
} else {
lenMax = max(lenMax, key)
}
}
for key in removalList.keys {
mutLengthNodeMap.removeValue(forKey: key)
lengthNodeMap.removeValue(forKey: key)
}
mutMaximumLength = lenMax
maxLength = lenMax
}
///
/// - Parameters:
/// - length:
public func node(length: Int) -> Node? {
public func nodeOf(length: Int) -> Node? {
// 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 {
///
public class Node {
///
private var mutKey: String = ""
///
private var mutScore: Double = 0
///
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 class Node: Equatable, Hashable {
public static func == (lhs: Megrez.Node, rhs: Megrez.Node) -> Bool {
lhs.key == rhs.key && lhs.score == rhs.score && lhs.unigrams == rhs.unigrams && lhs.bigrams == rhs.bigrams
&& lhs.candidates == rhs.candidates && lhs.valueUnigramIndexMap == rhs.valueUnigramIndexMap
&& lhs.precedingBigramMap == rhs.precedingBigramMap && lhs.isCandidateFixed == rhs.isCandidateFixed
&& lhs.selectedUnigramIndex == rhs.selectedUnigramIndex
}
///
public var candidates: [KeyValuePaired] { mutCandidates }
///
public var isCandidateFixed: Bool { mutCandidateFixed }
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(score)
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 {
mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePaired() : mutCandidates[mutSelectedUnigramIndex]
public var currentPair: KeyValuePaired {
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:
/// - key:
/// - unigrams:
/// - bigrams:
public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) {
mutKey = key
mutUnigrams = unigrams
mutBigrams = bigrams
public init(key: String = "", unigrams: [Megrez.Unigram] = [], bigrams: [Megrez.Bigram] = []) {
self.key = key
self.unigrams = unigrams
self.bigrams = bigrams
mutUnigrams.sort {
self.unigrams.sort {
$0.score > $1.score
}
if !mutUnigrams.isEmpty {
mutScore = mutUnigrams[0].score
if !self.unigrams.isEmpty {
score = unigrams[0].score
}
for (i, gram) in mutUnigrams.enumerated() {
mutValueUnigramIndexMap[gram.keyValue.value] = i
mutCandidates.append(gram.keyValue)
for (i, gram) in self.unigrams.enumerated() {
valueUnigramIndexMap[gram.keyValue.value] = i
candidates.append(gram.keyValue)
}
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:
/// - precedingKeyValues:
public func primeNodeWith(precedingKeyValues: [KeyValuePaired]) {
var newIndex = mutSelectedUnigramIndex
var max = mutScore
var newIndex = selectedUnigramIndex
var max = score
if !isCandidateFixed {
for neta in precedingKeyValues {
let bigrams = mutPrecedingBigramMap[neta] ?? []
let bigrams = precedingBigramMap[neta] ?? []
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
}
}
}
mutScore = max
mutSelectedUnigramIndex = newIndex
score = max
selectedUnigramIndex = newIndex
}
///
@ -126,17 +136,17 @@ extension Megrez {
/// - fix:
public func selectCandidateAt(index: Int = 0, fix: Bool = false) {
let index = abs(index)
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index
mutCandidateFixed = fix
mutScore = kSelectedCandidateScore
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = fix
score = Megrez.Node.kSelectedCandidateScore
}
///
public func resetCandidate() {
mutSelectedUnigramIndex = 0
mutCandidateFixed = false
if !mutUnigrams.isEmpty {
mutScore = mutUnigrams[0].score
selectedUnigramIndex = 0
isCandidateFixed = false
if !unigrams.isEmpty {
score = unigrams[0].score
}
}
@ -146,16 +156,26 @@ extension Megrez {
/// - score:
public func selectFloatingCandidateAt(index: Int, score: Double) {
let index = abs(index) //
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index
mutCandidateFixed = false
mutScore = score
selectedUnigramIndex = index >= unigrams.count ? 0 : index
isCandidateFixed = false
self.score = score
}
///
/// - Parameters:
/// - candidate:
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 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.
*/
public protocol LanguageModelProtocol {
public protocol LangModelProtocol {
///
func unigramsFor(key: String) -> [Megrez.Unigram]
@ -36,7 +36,7 @@ public protocol LanguageModelProtocol {
extension Megrez {
/// 使
open class LanguageModel: LanguageModelProtocol {
open class LangModel: LangModelProtocol {
public init() {}
// Swift

View File

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

View File

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

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" = "Clear Memorized Phrases";
// The followings are the category names used in the Symbol menu.
"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";
"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";
"Show Hanyu-Pinyin in the inline composition buffer & tooltip" = "Show Hanyu-Pinyin in the inline composition buffer & tooltip";
"Show page buttons in candidate window" = "Show page buttons in candidate window";
"Simplified Chinese" = "Simplified Chinese";
"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" = "Clear Memorized Phrases";
// The followings are the category names used in the Symbol menu.
"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";
"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";
"Show Hanyu-Pinyin in the inline composition buffer & tooltip" = "Show Hanyu-Pinyin in the inline composition buffer & tooltip";
"Show page buttons in candidate window" = "Show page buttons in candidate window";
"Simplified Chinese" = "Simplified Chinese";
"Space & ESC Key:" = "Space & ESC Key:";

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "繁体中国語核心辞書読込中…";
"Core Dict loading complete." = "核心辞書読込完了";
"Optimize Memorized Phrases" = "臨時記憶資料を整う";
"Clear Memorized Phrases" = "臨時記憶資料を削除";
// The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "常用";
@ -146,7 +147,7 @@
"Push the cursor in front of the phrase after selection" = "候補選択の直後、すぐカーソルを単語の向こうに推し進める";
"Secondary Pinyin with Numeral Intonation" = "国音二式 (ローマ字+数字音調)";
"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" = "入力候補陳列の側にページボタンを表示";
"Simplified Chinese" = "簡体中国語";
"Space & ESC Key:" = "ESC と Space:";

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "载入繁体中文核心辞典…";
"Core Dict loading complete." = "核心辞典载入完毕";
"Optimize Memorized Phrases" = "精简临时记忆语汇资料";
"Clear Memorized Phrases" = "清除临时记忆语汇资料";
// The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "常用";
@ -147,7 +148,7 @@
"Push the cursor in front of the phrase after selection" = "在选字后将游标置于该字词的前方";
"Secondary Pinyin with Numeral Intonation" = "国音二式+数字标调";
"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" = "在选字窗内显示翻页按钮";
"Simplified Chinese" = "简体中文";
"Space & ESC Key:" = "ESC 与空格键:";

View File

@ -63,6 +63,7 @@
"Loading CHT Core Dict..." = "載入繁體中文核心辭典…";
"Core Dict loading complete." = "核心辭典載入完畢";
"Optimize Memorized Phrases" = "精簡臨時記憶語彙資料";
"Clear Memorized Phrases" = "清除臨時記憶語彙資料";
// The followings are the category names used in the Symbol menu.
"catCommonSymbols" = "常用";
@ -146,7 +147,7 @@
"Push the cursor in front of the phrase after selection" = "在選字後將游標置於該字詞的前方";
"Secondary Pinyin with Numeral Intonation" = "國音二式+數字標調";
"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" = "在選字窗內顯示翻頁按鈕";
"Simplified Chinese" = "簡體中文";
"Space & ESC Key:" = "ESC 與空格鍵:";

View File

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

View File

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

View File

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

View File

@ -269,7 +269,7 @@
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vwf-Kq-s8M" userLabel="chkShowHanyuPinyinInCompositionBuffer">
<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"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>

View File

@ -74,7 +74,7 @@
"ueU-Rz-a1C.title" = "Choose the behavior of (Shift+)Tab key in the candidate window.";
"Uyz-xL-TVN.title" = "Output Settings";
"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.";
"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";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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