Repo // Use KeyValuePaired as candidate unit.
This commit is contained in:
parent
5f654c842e
commit
828575a08a
|
@ -139,9 +139,9 @@ enum InputState {
|
||||||
/// 因為逐字選字模式不需要在組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。
|
/// 因為逐字選字模式不需要在組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。
|
||||||
class AssociatedPhrases: InputStateProtocol {
|
class AssociatedPhrases: InputStateProtocol {
|
||||||
public var type: StateType { .ofAssociatedPhrases }
|
public var type: StateType { .ofAssociatedPhrases }
|
||||||
private(set) var candidates: [String] = []
|
private(set) var candidates: [(String, String)] = []
|
||||||
private(set) var isTypingVertical: Bool = false
|
private(set) var isTypingVertical: Bool = false
|
||||||
init(candidates: [String], isTypingVertical: Bool) {
|
init(candidates: [(String, String)], isTypingVertical: Bool) {
|
||||||
self.candidates = candidates
|
self.candidates = candidates
|
||||||
self.isTypingVertical = isTypingVertical
|
self.isTypingVertical = isTypingVertical
|
||||||
}
|
}
|
||||||
|
@ -407,10 +407,10 @@ enum InputState {
|
||||||
/// .ChoosingCandidate: 叫出選字窗、允許使用者選字。
|
/// .ChoosingCandidate: 叫出選字窗、允許使用者選字。
|
||||||
class ChoosingCandidate: NotEmpty {
|
class ChoosingCandidate: NotEmpty {
|
||||||
override public var type: StateType { .ofChooseCandidate }
|
override public var type: StateType { .ofChooseCandidate }
|
||||||
private(set) var candidates: [String]
|
private(set) var candidates: [(String, String)]
|
||||||
private(set) var isTypingVertical: Bool
|
private(set) var isTypingVertical: Bool
|
||||||
|
|
||||||
init(composingBuffer: String, cursorIndex: Int, candidates: [String], isTypingVertical: Bool) {
|
init(composingBuffer: String, cursorIndex: Int, candidates: [(String, String)], isTypingVertical: Bool) {
|
||||||
self.candidates = candidates
|
self.candidates = candidates
|
||||||
self.isTypingVertical = isTypingVertical
|
self.isTypingVertical = isTypingVertical
|
||||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||||
|
@ -432,7 +432,7 @@ enum InputState {
|
||||||
self.node = node
|
self.node = node
|
||||||
let candidates = node.children?.map(\.title) ?? [String]()
|
let candidates = node.children?.map(\.title) ?? [String]()
|
||||||
super.init(
|
super.init(
|
||||||
composingBuffer: "", cursorIndex: 0, candidates: candidates,
|
composingBuffer: "", cursorIndex: 0, candidates: candidates.map { ("", $0) },
|
||||||
isTypingVertical: isTypingVertical
|
isTypingVertical: isTypingVertical
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,10 +149,10 @@ class KeyHandler {
|
||||||
/// - Parameter key: 給定的聯想詞的開頭字。
|
/// - Parameter key: 給定的聯想詞的開頭字。
|
||||||
/// - Returns: 抓取到的聯想詞陣列。
|
/// - Returns: 抓取到的聯想詞陣列。
|
||||||
/// 不會是 nil,但那些負責接收結果的函式會對空白陣列結果做出正確的處理。
|
/// 不會是 nil,但那些負責接收結果的函式會對空白陣列結果做出正確的處理。
|
||||||
func buildAssociatePhraseArray(withKey key: String) -> [String] {
|
func buildAssociatePhraseArray(withKey key: String) -> [(String, String)] {
|
||||||
var arrResult: [String] = []
|
var arrResult: [(String, String)] = []
|
||||||
if currentLM.hasAssociatedPhrasesFor(key: key) {
|
if currentLM.hasAssociatedPhrasesFor(key: key) {
|
||||||
arrResult.append(contentsOf: currentLM.associatedPhrasesFor(key: key))
|
arrResult = currentLM.associatedPhrasesFor(key: key).map { ("", $0) }
|
||||||
}
|
}
|
||||||
return arrResult
|
return arrResult
|
||||||
}
|
}
|
||||||
|
@ -162,21 +162,22 @@ class KeyHandler {
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - value: 給定之候選字字串。
|
/// - value: 給定之候選字字串。
|
||||||
/// - respectCursorPushing: 若該選項為 true,則會在選字之後始終將游標推送至選字厚的節錨的前方。
|
/// - respectCursorPushing: 若該選項為 true,則會在選字之後始終將游標推送至選字厚的節錨的前方。
|
||||||
func fixNode(value: String, respectCursorPushing: Bool = true) {
|
func fixNode(candidate: (String, String), respectCursorPushing: Bool = true) {
|
||||||
|
let theCandidate: Megrez.KeyValuePaired = .init(key: candidate.0, value: candidate.1)
|
||||||
let adjustedCursor = max(0, min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength))
|
let adjustedCursor = max(0, min(actualCandidateCursor + (mgrPrefs.useRearCursorMode ? 1 : 0), compositorLength))
|
||||||
// 開始讓半衰模組觀察目前的狀況。
|
// 開始讓半衰模組觀察目前的狀況。
|
||||||
let selectedNode: Megrez.NodeAnchor = compositor.fixNodeSelectedCandidate(value, at: adjustedCursor)
|
let selectedNode: Megrez.NodeAnchor = compositor.fixNodeWithCandidate(theCandidate, at: adjustedCursor)
|
||||||
// 不要針對逐字選字模式啟用臨時半衰記憶模型。
|
// 不要針對逐字選字模式啟用臨時半衰記憶模型。
|
||||||
if !mgrPrefs.useSCPCTypingMode {
|
if !mgrPrefs.useSCPCTypingMode {
|
||||||
var addToUserOverrideModel = true
|
var addToUserOverrideModel = true
|
||||||
// 所有讀音數與字符數不匹配的情況均不得塞入半衰記憶模組。
|
// 所有讀音數與字符數不匹配的情況均不得塞入半衰記憶模組。
|
||||||
if selectedNode.spanLength != value.count {
|
if selectedNode.spanLength != theCandidate.value.count {
|
||||||
IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
|
IME.prtDebugIntel("UOM: SpanningLength != value.count, dismissing.")
|
||||||
addToUserOverrideModel = false
|
addToUserOverrideModel = false
|
||||||
}
|
}
|
||||||
if addToUserOverrideModel {
|
if addToUserOverrideModel {
|
||||||
// 威注音的 SymbolLM 的 Score 是 -12,符合該條件的內容不得塞入半衰記憶模組。
|
// 威注音的 SymbolLM 的 Score 是 -12,符合該條件的內容不得塞入半衰記憶模組。
|
||||||
if selectedNode.node.scoreFor(candidate: value) <= -12 {
|
if selectedNode.node.scoreForPaired(candidate: theCandidate) <= -12 {
|
||||||
IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
|
IME.prtDebugIntel("UOM: Score <= -12, dismissing.")
|
||||||
addToUserOverrideModel = false
|
addToUserOverrideModel = false
|
||||||
}
|
}
|
||||||
|
@ -186,7 +187,7 @@ class KeyHandler {
|
||||||
// 令半衰記憶模組觀測給定的三元圖。
|
// 令半衰記憶模組觀測給定的三元圖。
|
||||||
// 這個過程會讓半衰引擎根據當前上下文生成三元圖索引鍵。
|
// 這個過程會讓半衰引擎根據當前上下文生成三元圖索引鍵。
|
||||||
currentUOM.observe(
|
currentUOM.observe(
|
||||||
walkedAnchors: walkedAnchors, cursorIndex: adjustedCursor, candidate: value,
|
walkedAnchors: walkedAnchors, cursorIndex: adjustedCursor, candidate: theCandidate.value,
|
||||||
timestamp: NSDate().timeIntervalSince1970
|
timestamp: NSDate().timeIntervalSince1970
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -211,21 +212,21 @@ class KeyHandler {
|
||||||
for anchor in walkedAnchors {
|
for anchor in walkedAnchors {
|
||||||
if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
|
if index >= width - kMaxComposingBufferNeedsToWalkSize { break }
|
||||||
if anchor.node.score < Megrez.Node.kSelectedCandidateScore {
|
if anchor.node.score < Megrez.Node.kSelectedCandidateScore {
|
||||||
compositor.fixNodeSelectedCandidate(anchor.node.currentPair.value, at: index + anchor.spanLength)
|
compositor.fixNodeWithCandidate(anchor.node.currentPair, at: index + anchor.spanLength)
|
||||||
}
|
}
|
||||||
index += anchor.spanLength
|
index += anchor.spanLength
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 獲取候選字詞陣列資料內容。
|
/// 獲取候選字詞(包含讀音)陣列資料內容。
|
||||||
func getCandidatesArray(fixOrder: Bool = true) -> [String] {
|
func getCandidatesArray(fixOrder: Bool = true) -> [(String, String)] {
|
||||||
var arrAnchors: [Megrez.NodeAnchor] = rawAnchorsOfNodes
|
var arrAnchors: [Megrez.NodeAnchor] = rawAnchorsOfNodes
|
||||||
var arrCandidates: [String] = []
|
var arrCandidates: [Megrez.KeyValuePaired] = .init()
|
||||||
|
|
||||||
/// 原理:nodes 這個回饋結果包含一堆子陣列,分別對應不同詞長的候選字。
|
/// 原理:nodes 這個回饋結果包含一堆子陣列,分別對應不同詞長的候選字。
|
||||||
/// 這裡先對陣列排序、讓最長候選字的子陣列的優先權最高。
|
/// 這裡先對陣列排序、讓最長候選字的子陣列的優先權最高。
|
||||||
/// 這個過程不會傷到子陣列內部的排序。
|
/// 這個過程不會傷到子陣列內部的排序。
|
||||||
if arrAnchors.isEmpty { return arrCandidates }
|
if arrAnchors.isEmpty { return .init() }
|
||||||
|
|
||||||
// 讓更長的節錨排序靠前。
|
// 讓更長的節錨排序靠前。
|
||||||
arrAnchors = arrAnchors.stableSort { $0.keyLength > $1.keyLength }
|
arrAnchors = arrAnchors.stableSort { $0.keyLength > $1.keyLength }
|
||||||
|
@ -235,19 +236,19 @@ class KeyHandler {
|
||||||
// 選字窗的內容的康熙轉換 / JIS 轉換不能放在這裡處理,會影響選字有效性。
|
// 選字窗的內容的康熙轉換 / JIS 轉換不能放在這裡處理,會影響選字有效性。
|
||||||
// 選字的原理是拿著具體的候選字詞的字串去當前的節錨下找出對應的候選字詞(X元圖)。
|
// 選字的原理是拿著具體的候選字詞的字串去當前的節錨下找出對應的候選字詞(X元圖)。
|
||||||
// 一旦在這裡轉換了,節錨內的某些元圖就無法被選中。
|
// 一旦在這裡轉換了,節錨內的某些元圖就無法被選中。
|
||||||
arrCandidates.append(currentCandidate.value)
|
arrCandidates.append(.init(key: currentCandidate.key, value: currentCandidate.value))
|
||||||
}
|
}
|
||||||
// 決定是否根據半衰記憶模組的建議來調整候選字詞的順序。
|
// 決定是否根據半衰記憶模組的建議來調整候選字詞的順序。
|
||||||
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel || mgrPrefs.useSCPCTypingMode || fixOrder {
|
if !mgrPrefs.fetchSuggestionsFromUserOverrideModel || mgrPrefs.useSCPCTypingMode || fixOrder {
|
||||||
return arrCandidates
|
return arrCandidates.map { ($0.key, $0.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
let arrSuggestedUnigrams: [Megrez.Unigram] = fetchSuggestedCandidates().stableSort { $0.score > $1.score }
|
let arrSuggestedUnigrams: [Megrez.Unigram] = fetchSuggestedCandidates().stableSort { $0.score > $1.score }
|
||||||
let arrSuggestedCandidates: [String] = arrSuggestedUnigrams.map(\.keyValue.value)
|
let arrSuggestedCandidates: [Megrez.KeyValuePaired] = arrSuggestedUnigrams.map(\.keyValue)
|
||||||
arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates
|
arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates
|
||||||
arrCandidates = arrCandidates.deduplicate
|
arrCandidates = arrCandidates.deduplicate
|
||||||
arrCandidates = arrCandidates.stableSort { $0.count > $1.count }
|
arrCandidates = arrCandidates.stableSort { $0.key.split(separator: "-").count > $1.key.split(separator: "-").count }
|
||||||
return arrCandidates
|
return arrCandidates.map { ($0.key, $0.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 向半衰引擎詢問可能的選字建議。拿到的結果會是一個單元圖陣列。
|
/// 向半衰引擎詢問可能的選字建議。拿到的結果會是一個單元圖陣列。
|
||||||
|
|
|
@ -274,7 +274,7 @@ extension KeyHandler {
|
||||||
|
|
||||||
// MARK: End Key
|
// MARK: End Key
|
||||||
|
|
||||||
var candidates: [String]!
|
var candidates: [(String, String)]!
|
||||||
|
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
candidates = state.candidates
|
candidates = state.candidates
|
||||||
|
|
|
@ -225,7 +225,7 @@ extension KeyHandler {
|
||||||
)
|
)
|
||||||
if choosingCandidates.candidates.count == 1 {
|
if choosingCandidates.candidates.count == 1 {
|
||||||
clear()
|
clear()
|
||||||
let text: String = choosingCandidates.candidates.first ?? ""
|
let text: String = choosingCandidates.candidates.first?.1 ?? ""
|
||||||
stateCallback(InputState.Committing(textToCommit: text))
|
stateCallback(InputState.Committing(textToCommit: text))
|
||||||
|
|
||||||
if !mgrPrefs.associatedPhrasesEnabled {
|
if !mgrPrefs.associatedPhrasesEnabled {
|
||||||
|
|
|
@ -330,8 +330,8 @@ extension KeyHandler {
|
||||||
)
|
)
|
||||||
if candidateState.candidates.count == 1 {
|
if candidateState.candidates.count == 1 {
|
||||||
clear()
|
clear()
|
||||||
if let strtextToCommit: String = candidateState.candidates.first {
|
if let candidateToCommit: (String, String) = candidateState.candidates.first {
|
||||||
stateCallback(InputState.Committing(textToCommit: strtextToCommit))
|
stateCallback(InputState.Committing(textToCommit: candidateToCommit.1))
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
} else {
|
} else {
|
||||||
stateCallback(candidateState)
|
stateCallback(candidateState)
|
||||||
|
@ -802,7 +802,7 @@ extension KeyHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentNode = currentAnchor.node
|
let currentNode = currentAnchor.node
|
||||||
let currentValue = currentNode.currentPair.value
|
let currentPaired: Megrez.KeyValuePaired = currentNode.currentPair
|
||||||
|
|
||||||
var currentIndex = 0
|
var currentIndex = 0
|
||||||
if currentNode.score < Megrez.Node.kSelectedCandidateScore {
|
if currentNode.score < Megrez.Node.kSelectedCandidateScore {
|
||||||
|
@ -814,14 +814,14 @@ extension KeyHandler {
|
||||||
/// 選中的話,則使用者可以直接摁下本函式對應的按鍵來輪替候選字即可。
|
/// 選中的話,則使用者可以直接摁下本函式對應的按鍵來輪替候選字即可。
|
||||||
/// (預設情況下是 (Shift+)Tab 來做正 (反) 向切換,但也可以用
|
/// (預設情況下是 (Shift+)Tab 來做正 (反) 向切換,但也可以用
|
||||||
/// Shift(+CMD)+Space 來切換、以應對臉書綁架 Tab 鍵的情況。
|
/// Shift(+CMD)+Space 來切換、以應對臉書綁架 Tab 鍵的情況。
|
||||||
if candidates[0] == currentValue {
|
if candidates[0].0 == currentPaired.key, candidates[0].1 == currentPaired.value {
|
||||||
/// 如果第一個候選字詞是當前節點的候選字詞的值的話,
|
/// 如果第一個候選字詞是當前節點的候選字詞的值的話,
|
||||||
/// 那就切到下一個(或上一個,也就是最後一個)候選字詞。
|
/// 那就切到下一個(或上一個,也就是最後一個)候選字詞。
|
||||||
currentIndex = reverseModifier ? candidates.count - 1 : 1
|
currentIndex = reverseModifier ? candidates.count - 1 : 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for candidate in candidates {
|
for candidate in candidates {
|
||||||
if candidate == currentValue {
|
if candidate.0 == currentPaired.key, candidate.1 == currentPaired.value {
|
||||||
if reverseModifier {
|
if reverseModifier {
|
||||||
if currentIndex == 0 {
|
if currentIndex == 0 {
|
||||||
currentIndex = candidates.count - 1
|
currentIndex = candidates.count - 1
|
||||||
|
@ -841,7 +841,7 @@ extension KeyHandler {
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fixNode(value: candidates[currentIndex], respectCursorPushing: false)
|
fixNode(candidate: candidates[currentIndex], respectCursorPushing: false)
|
||||||
|
|
||||||
stateCallback(buildInputtingState)
|
stateCallback(buildInputtingState)
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -81,7 +81,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
|
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
|
||||||
-> String
|
-> (String, String)
|
||||||
{
|
{
|
||||||
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
|
@ -89,7 +89,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.AssociatedPhrases {
|
||||||
return state.candidates[index]
|
return state.candidates[index]
|
||||||
}
|
}
|
||||||
return ""
|
return ("", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int) {
|
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int) {
|
||||||
|
@ -112,7 +112,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
|
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
let selectedValue = state.candidates[index]
|
let selectedValue = state.candidates[index]
|
||||||
keyHandler.fixNode(value: selectedValue, respectCursorPushing: true)
|
keyHandler.fixNode(candidate: selectedValue, respectCursorPushing: true)
|
||||||
|
|
||||||
let inputting = keyHandler.buildInputtingState
|
let inputting = keyHandler.buildInputtingState
|
||||||
|
|
||||||
|
@ -137,10 +137,10 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
|
|
||||||
if let state = state as? InputState.AssociatedPhrases {
|
if let state = state as? InputState.AssociatedPhrases {
|
||||||
let selectedValue = state.candidates[index]
|
let selectedValue = state.candidates[index]
|
||||||
handle(state: InputState.Committing(textToCommit: selectedValue))
|
handle(state: InputState.Committing(textToCommit: selectedValue.1))
|
||||||
if mgrPrefs.associatedPhrasesEnabled,
|
if mgrPrefs.associatedPhrasesEnabled,
|
||||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||||
withKey: selectedValue, isTypingVertical: state.isTypingVertical
|
withKey: selectedValue.1, isTypingVertical: state.isTypingVertical
|
||||||
), !associatePhrases.candidates.isEmpty
|
), !associatePhrases.candidates.isEmpty
|
||||||
{
|
{
|
||||||
handle(state: associatePhrases)
|
handle(state: associatePhrases)
|
||||||
|
|
|
@ -55,7 +55,7 @@ extension ctlInputMethod {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var isCandidateWindowVertical: Bool {
|
var isCandidateWindowVertical: Bool {
|
||||||
var candidates: [String] = []
|
var candidates: [(String, String)] = .init()
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
candidates = state.candidates
|
candidates = state.candidates
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.AssociatedPhrases {
|
||||||
|
@ -63,13 +63,11 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
if isTypingVertical { return true }
|
if isTypingVertical { return true }
|
||||||
// 以上是通用情形。接下來決定橫排輸入時是否使用縱排選字窗。
|
// 以上是通用情形。接下來決定橫排輸入時是否使用縱排選字窗。
|
||||||
candidates.sort {
|
// 因為在拿候選字陣列時已經排序過了,所以這裡不用再多排序。
|
||||||
$0.count > $1.count
|
|
||||||
}
|
|
||||||
// 測量每頁顯示候選字的累計總長度。如果太長的話就強制使用縱排候選字窗。
|
// 測量每頁顯示候選字的累計總長度。如果太長的話就強制使用縱排候選字窗。
|
||||||
// 範例:「屬實牛逼」(會有一大串各種各樣的「鼠食牛Beer」的 emoji)。
|
// 範例:「屬實牛逼」(會有一大串各種各樣的「鼠食牛Beer」的 emoji)。
|
||||||
let maxCandidatesPerPage = mgrPrefs.candidateKeys.count
|
let maxCandidatesPerPage = mgrPrefs.candidateKeys.count
|
||||||
let firstPageCandidates = candidates[0..<min(maxCandidatesPerPage, candidates.count)]
|
let firstPageCandidates = candidates[0..<min(maxCandidatesPerPage, candidates.count)].map(\.1)
|
||||||
return firstPageCandidates.joined().count > Int(round(Double(maxCandidatesPerPage) * 1.8))
|
return firstPageCandidates.joined().count > Int(round(Double(maxCandidatesPerPage) * 1.8))
|
||||||
// 上面這句如果是 true 的話,就會是縱排;反之則為橫排。
|
// 上面這句如果是 true 的話,就會是縱排;反之則為橫排。
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class CandidateKeyLabel: NSObject {
|
||||||
public protocol ctlCandidateDelegate: AnyObject {
|
public protocol ctlCandidateDelegate: AnyObject {
|
||||||
func candidateCountForController(_ controller: ctlCandidate) -> Int
|
func candidateCountForController(_ controller: ctlCandidate) -> Int
|
||||||
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
|
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
|
||||||
-> String
|
-> (String, String)
|
||||||
func ctlCandidate(
|
func ctlCandidate(
|
||||||
_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int
|
_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int
|
||||||
)
|
)
|
||||||
|
|
|
@ -559,7 +559,7 @@ extension ctlCandidateUniversal {
|
||||||
}
|
}
|
||||||
|
|
||||||
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
|
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
|
||||||
var candidates = [String]()
|
var candidates = [(String, String)]()
|
||||||
let count = delegate.candidateCountForController(self)
|
let count = delegate.candidateCountForController(self)
|
||||||
let keyLabelCount = keyLabels.count
|
let keyLabelCount = keyLabels.count
|
||||||
|
|
||||||
|
@ -569,7 +569,7 @@ extension ctlCandidateUniversal {
|
||||||
candidates.append(candidate)
|
candidates.append(candidate)
|
||||||
}
|
}
|
||||||
candidateView.set(
|
candidateView.set(
|
||||||
keyLabels: keyLabels.map(\.displayedText), displayedCandidates: candidates
|
keyLabels: keyLabels.map(\.displayedText), displayedCandidates: candidates.map(\.1)
|
||||||
)
|
)
|
||||||
var newSize = candidateView.sizeForView
|
var newSize = candidateView.sizeForView
|
||||||
var frameRect = candidateView.frame
|
var frameRect = candidateView.frame
|
||||||
|
|
|
@ -365,7 +365,7 @@ class KeyHandlerTestsNormalCHS: XCTestCase {
|
||||||
|
|
||||||
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
|
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
XCTAssertTrue(state.candidates.contains("!"))
|
XCTAssertTrue(state.candidates.map(\.1).contains("!"))
|
||||||
}
|
}
|
||||||
mgrPrefs.halfWidthPunctuationEnabled = enabled
|
mgrPrefs.halfWidthPunctuationEnabled = enabled
|
||||||
}
|
}
|
||||||
|
@ -993,7 +993,7 @@ class KeyHandlerTestsNormalCHS: XCTestCase {
|
||||||
XCTAssertEqual(state.composingBuffer, "你")
|
XCTAssertEqual(state.composingBuffer, "你")
|
||||||
XCTAssertEqual(state.cursorIndex, 1)
|
XCTAssertEqual(state.cursorIndex, 1)
|
||||||
let candidates = state.candidates
|
let candidates = state.candidates
|
||||||
XCTAssertTrue(candidates.contains("你"))
|
XCTAssertTrue(candidates.map(\.1).contains("你"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1030,7 +1030,7 @@ class KeyHandlerTestsNormalCHS: XCTestCase {
|
||||||
XCTAssertEqual(state.composingBuffer, "你")
|
XCTAssertEqual(state.composingBuffer, "你")
|
||||||
XCTAssertEqual(state.cursorIndex, 1)
|
XCTAssertEqual(state.cursorIndex, 1)
|
||||||
let candidates = state.candidates
|
let candidates = state.candidates
|
||||||
XCTAssertTrue(candidates.contains("你"))
|
XCTAssertTrue(candidates.map(\.1).contains("你"))
|
||||||
}
|
}
|
||||||
mgrPrefs.chooseCandidateUsingSpace = enabled
|
mgrPrefs.chooseCandidateUsingSpace = enabled
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ class KeyHandlerTestsSCPCCHT: XCTestCase {
|
||||||
|
|
||||||
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
|
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
XCTAssertTrue(state.candidates.contains(","))
|
XCTAssertTrue(state.candidates.map(\.1).contains(","))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +238,7 @@ class KeyHandlerTestsSCPCCHT: XCTestCase {
|
||||||
|
|
||||||
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
|
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
XCTAssertTrue(state.candidates.contains("你"))
|
XCTAssertTrue(state.candidates.map(\.1).contains("你"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ class KeyHandlerTestsSCPCCHT: XCTestCase {
|
||||||
print("\(state)")
|
print("\(state)")
|
||||||
// XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)")
|
// XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)")
|
||||||
// if let state = state as? InputState.AssociatedPhrases {
|
// if let state = state as? InputState.AssociatedPhrases {
|
||||||
// XCTAssertTrue(state.candidates.contains("百五"))
|
// XCTAssertTrue(state.candidates.map(\.1).contains("百五"))
|
||||||
// }
|
// }
|
||||||
mgrPrefs.associatedPhrasesEnabled = enabled
|
mgrPrefs.associatedPhrasesEnabled = enabled
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue