Repo // Move IMEState-related protocols to Shared package.
This commit is contained in:
parent
cab7eb28f7
commit
1648152b24
|
@ -0,0 +1,64 @@
|
|||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
// ... with NTL restriction stating that:
|
||||
// No trademark license is granted to use the trade names, trademarks, service
|
||||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Cocoa
|
||||
|
||||
// 所有 IMEState 均遵守該協定:
|
||||
public protocol IMEStateProtocol {
|
||||
var type: StateType { get }
|
||||
var data: StateDataProtocol { get }
|
||||
var isASCIIMode: Bool { get set }
|
||||
var isVerticalTyping: Bool { get set }
|
||||
var isVerticalCandidateWindow: Bool { get set }
|
||||
var candidates: [(String, String)] { get set }
|
||||
var hasComposition: Bool { get }
|
||||
var isCandidateContainer: Bool { get }
|
||||
var displayedText: String { get }
|
||||
var displayedTextConverted: String { get }
|
||||
var textToCommit: String { get set }
|
||||
var tooltip: String { get set }
|
||||
var attributedString: NSAttributedString { get }
|
||||
var convertedToInputting: IMEStateProtocol { get }
|
||||
var isFilterable: Bool { get }
|
||||
var isMarkedLengthValid: Bool { get }
|
||||
var node: CandidateNode { get set }
|
||||
var displayTextSegments: [String] { get }
|
||||
var tooltipBackupForInputting: String { get set }
|
||||
var markedRange: Range<Int> { get }
|
||||
var u16MarkedRange: Range<Int> { get }
|
||||
var u16Cursor: Int { get }
|
||||
var cursor: Int { get set }
|
||||
var marker: Int { get set }
|
||||
}
|
||||
|
||||
public protocol StateDataProtocol {
|
||||
var cursor: Int { get set }
|
||||
var marker: Int { get set }
|
||||
var markedRange: Range<Int> { get }
|
||||
var u16MarkedRange: Range<Int> { get }
|
||||
var u16Cursor: Int { get }
|
||||
var textToCommit: String { get set }
|
||||
var markedReadings: [String] { get set }
|
||||
var displayTextSegments: [String] { get set }
|
||||
var isFilterable: Bool { get }
|
||||
var isVerticalTyping: Bool { get set }
|
||||
var isMarkedLengthValid: Bool { get }
|
||||
var candidates: [(String, String)] { get set }
|
||||
var displayedText: String { get set }
|
||||
var displayedTextConverted: String { get }
|
||||
var tooltipBackupForInputting: String { get set }
|
||||
var tooltip: String { get set }
|
||||
var attributedStringNormal: NSAttributedString { get }
|
||||
var attributedStringMarking: NSAttributedString { get }
|
||||
var attributedStringPlaceholder: NSAttributedString { get }
|
||||
var userPhraseDumped: String { get }
|
||||
var userPhraseDumpedConverted: String { get }
|
||||
var doesUserPhraseExist: Bool { get }
|
||||
var tooltipColorState: TooltipColorState { get set }
|
||||
mutating func updateTooltipForMarking()
|
||||
}
|
|
@ -103,6 +103,33 @@ public enum UserDef: String, CaseIterable {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Tooltip Color States
|
||||
|
||||
public enum TooltipColorState {
|
||||
case normal
|
||||
case redAlert
|
||||
case warning
|
||||
case denialOverflow
|
||||
case denialInsufficiency
|
||||
case prompt
|
||||
}
|
||||
|
||||
// MARK: - IMEState types.
|
||||
|
||||
// 用以讓每個狀態自描述的 enum。
|
||||
public enum StateType: String {
|
||||
case ofDeactivated = "Deactivated"
|
||||
case ofEmpty = "Empty"
|
||||
case ofAbortion = "Abortion" // 該狀態會自動轉為 Empty
|
||||
case ofCommitting = "Committing"
|
||||
case ofAssociates = "Associates"
|
||||
case ofNotEmpty = "NotEmpty"
|
||||
case ofInputting = "Inputting"
|
||||
case ofMarking = "Marking"
|
||||
case ofCandidates = "Candidates"
|
||||
case ofSymbolTable = "SymbolTable"
|
||||
}
|
||||
|
||||
// MARK: - Parser for Syllable composer
|
||||
|
||||
public enum KeyboardParser: Int, CaseIterable {
|
||||
|
|
|
@ -15,6 +15,7 @@ let package = Package(
|
|||
dependencies: [
|
||||
.package(path: "../Fuziki_NSAttributedTextView"),
|
||||
.package(path: "../vChewing_CocoaExtension"),
|
||||
.package(path: "../vChewing_Shared"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
|
@ -22,6 +23,7 @@ let package = Package(
|
|||
dependencies: [
|
||||
.product(name: "NSAttributedTextView", package: "Fuziki_NSAttributedTextView"),
|
||||
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
|
||||
.product(name: "Shared", package: "vChewing_Shared"),
|
||||
]
|
||||
)
|
||||
]
|
||||
|
|
|
@ -9,17 +9,9 @@
|
|||
import Cocoa
|
||||
import CocoaExtension
|
||||
import NSAttributedTextView
|
||||
import Shared
|
||||
|
||||
public class TooltipUI: NSWindowController {
|
||||
public enum ColorStates {
|
||||
case normal
|
||||
case redAlert
|
||||
case warning
|
||||
case denialOverflow
|
||||
case denialInsufficiency
|
||||
case prompt
|
||||
}
|
||||
|
||||
private var messageText: NSAttributedTooltipTextView
|
||||
private var tooltip: String = "" {
|
||||
didSet {
|
||||
|
@ -68,7 +60,7 @@ public class TooltipUI: NSWindowController {
|
|||
set(windowTopLeftPoint: point, bottomOutOfScreenAdjustmentHeight: heightDelta)
|
||||
}
|
||||
|
||||
public func setColor(state: ColorStates) {
|
||||
public func setColor(state: TooltipColorState) {
|
||||
var backgroundColor = NSColor.controlBackgroundColor
|
||||
var textColor = NSColor.textColor
|
||||
switch state {
|
||||
|
|
|
@ -7,47 +7,7 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import LangModelAssembly
|
||||
|
||||
// 用以讓每個狀態自描述的 enum。
|
||||
public enum StateType: String {
|
||||
case ofDeactivated = "Deactivated"
|
||||
case ofEmpty = "Empty"
|
||||
case ofAbortion = "Abortion" // 該狀態會自動轉為 Empty
|
||||
case ofCommitting = "Committing"
|
||||
case ofAssociates = "Associates"
|
||||
case ofNotEmpty = "NotEmpty"
|
||||
case ofInputting = "Inputting"
|
||||
case ofMarking = "Marking"
|
||||
case ofCandidates = "Candidates"
|
||||
case ofSymbolTable = "SymbolTable"
|
||||
}
|
||||
|
||||
// 所有 IMEState 均遵守該協定:
|
||||
public protocol IMEStateProtocol {
|
||||
var type: StateType { get }
|
||||
var data: StateData { get }
|
||||
var isASCIIMode: Bool { get set }
|
||||
var isVerticalTyping: Bool { get set }
|
||||
var isVerticalCandidateWindow: Bool { get set }
|
||||
var candidates: [(String, String)] { get }
|
||||
var hasComposition: Bool { get }
|
||||
var isCandidateContainer: Bool { get }
|
||||
var displayedText: String { get }
|
||||
var displayedTextConverted: String { get }
|
||||
var textToCommit: String { get set }
|
||||
var tooltip: String { get set }
|
||||
var attributedString: NSAttributedString { get }
|
||||
var convertedToInputting: IMEState { get }
|
||||
var isFilterable: Bool { get }
|
||||
var isMarkedLengthValid: Bool { get }
|
||||
var node: CandidateNode { get set }
|
||||
var displayTextSegments: [String] { get }
|
||||
var tooltipBackupForInputting: String { get set }
|
||||
var markedRange: Range<Int> { get }
|
||||
var cursor: Int { get }
|
||||
var u16MarkedRange: Range<Int> { get }
|
||||
var u16Cursor: Int { get }
|
||||
}
|
||||
import Shared
|
||||
|
||||
/// 用以呈現輸入法控制器(ctlInputMethod)的各種狀態。
|
||||
///
|
||||
|
@ -82,17 +42,17 @@ public protocol IMEStateProtocol {
|
|||
/// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。
|
||||
public struct IMEState: IMEStateProtocol {
|
||||
public var type: StateType = .ofEmpty
|
||||
public var data: StateData = .init()
|
||||
public var data: StateDataProtocol = StateData() as StateDataProtocol
|
||||
public var node: CandidateNode = .init(name: "")
|
||||
public var isASCIIMode = false
|
||||
public var isVerticalCandidateWindow = false
|
||||
init(_ data: StateData = .init(), type: StateType = .ofEmpty) {
|
||||
init(_ data: StateDataProtocol = StateData() as StateDataProtocol, type: StateType = .ofEmpty) {
|
||||
self.data = data
|
||||
self.type = type
|
||||
isVerticalTyping = ctlInputMethod.isVerticalTyping
|
||||
}
|
||||
|
||||
init(_ data: StateData = .init(), type: StateType = .ofEmpty, node: CandidateNode) {
|
||||
init(_ data: StateDataProtocol = StateData() as StateDataProtocol, type: StateType = .ofEmpty, node: CandidateNode) {
|
||||
self.data = data
|
||||
self.type = type
|
||||
self.node = node
|
||||
|
@ -108,14 +68,14 @@ extension IMEState {
|
|||
public static func ofAbortion() -> IMEState { .init(type: .ofAbortion) }
|
||||
public static func ofCommitting(textToCommit: String) -> IMEState {
|
||||
var result = IMEState(type: .ofCommitting)
|
||||
result.data.textToCommit = textToCommit
|
||||
result.textToCommit = textToCommit
|
||||
ChineseConverter.ensureCurrencyNumerals(target: &result.data.textToCommit)
|
||||
return result
|
||||
}
|
||||
|
||||
public static func ofAssociates(candidates: [(String, String)]) -> IMEState {
|
||||
var result = IMEState(type: .ofAssociates)
|
||||
result.data.candidates = candidates
|
||||
result.candidates = candidates
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -175,15 +135,24 @@ extension IMEState {
|
|||
extension IMEState {
|
||||
public var isFilterable: Bool { data.isFilterable }
|
||||
public var isMarkedLengthValid: Bool { data.isMarkedLengthValid }
|
||||
public var candidates: [(String, String)] { data.candidates }
|
||||
public var displayedText: String { data.displayedText }
|
||||
public var displayedTextConverted: String { data.displayedTextConverted }
|
||||
public var cursor: Int { data.cursor }
|
||||
public var displayTextSegments: [String] { data.displayTextSegments }
|
||||
public var markedRange: Range<Int> { data.markedRange }
|
||||
public var u16MarkedRange: Range<Int> { data.u16MarkedRange }
|
||||
public var u16Cursor: Int { data.u16Cursor }
|
||||
public var convertedToInputting: IMEState {
|
||||
|
||||
public var cursor: Int {
|
||||
get { data.cursor }
|
||||
set { data.cursor = newValue }
|
||||
}
|
||||
|
||||
public var marker: Int {
|
||||
get { data.marker }
|
||||
set { data.marker = newValue }
|
||||
}
|
||||
|
||||
public var convertedToInputting: IMEStateProtocol {
|
||||
if type == .ofInputting { return self }
|
||||
var result = IMEState.ofInputting(displayTextSegments: data.displayTextSegments, cursor: data.cursor)
|
||||
result.tooltip = data.tooltipBackupForInputting
|
||||
|
@ -191,22 +160,19 @@ extension IMEState {
|
|||
return result
|
||||
}
|
||||
|
||||
public var candidates: [(String, String)] {
|
||||
get { data.candidates }
|
||||
set { data.candidates = newValue }
|
||||
}
|
||||
|
||||
public var textToCommit: String {
|
||||
get {
|
||||
data.textToCommit
|
||||
}
|
||||
set {
|
||||
data.textToCommit = newValue
|
||||
}
|
||||
get { data.textToCommit }
|
||||
set { data.textToCommit = newValue }
|
||||
}
|
||||
|
||||
public var tooltip: String {
|
||||
get {
|
||||
data.tooltip
|
||||
}
|
||||
set {
|
||||
data.tooltip = newValue
|
||||
}
|
||||
get { data.tooltip }
|
||||
set { data.tooltip = newValue }
|
||||
}
|
||||
|
||||
public var attributedString: NSAttributedString {
|
||||
|
@ -237,11 +203,7 @@ extension IMEState {
|
|||
}
|
||||
|
||||
public var tooltipBackupForInputting: String {
|
||||
get {
|
||||
data.tooltipBackupForInputting
|
||||
}
|
||||
set {
|
||||
data.tooltipBackupForInputting = newValue
|
||||
}
|
||||
get { data.tooltipBackupForInputting }
|
||||
set { data.tooltipBackupForInputting = newValue }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Shared
|
||||
import Tekkon
|
||||
import TooltipUI
|
||||
|
||||
public struct StateData {
|
||||
public struct StateData: StateDataProtocol {
|
||||
private static var minCandidateLength: Int {
|
||||
PrefMgr.shared.allowBoostingSingleKanjiAsUserPhrase ? 1 : 2
|
||||
}
|
||||
|
@ -18,8 +19,8 @@ public struct StateData {
|
|||
Self.minCandidateLength...PrefMgr.shared.maxCandidateLength
|
||||
}
|
||||
|
||||
var displayedText: String = ""
|
||||
var displayedTextConverted: String {
|
||||
public var displayedText: String = ""
|
||||
public var displayedTextConverted: String {
|
||||
/// 先做繁簡轉換
|
||||
var result = ChineseConverter.kanjiConversionIfRequired(displayedText)
|
||||
if result.utf16.count != displayedText.utf16.count
|
||||
|
@ -32,19 +33,19 @@ public struct StateData {
|
|||
|
||||
// MARK: Cursor & Marker & Range for UTF8
|
||||
|
||||
var cursor: Int = 0 {
|
||||
public var cursor: Int = 0 {
|
||||
didSet {
|
||||
cursor = min(max(cursor, 0), displayedText.count)
|
||||
}
|
||||
}
|
||||
|
||||
var marker: Int = 0 {
|
||||
public var marker: Int = 0 {
|
||||
didSet {
|
||||
marker = min(max(marker, 0), displayedText.count)
|
||||
}
|
||||
}
|
||||
|
||||
var markedRange: Range<Int> {
|
||||
public var markedRange: Range<Int> {
|
||||
min(cursor, marker)..<max(cursor, marker)
|
||||
}
|
||||
|
||||
|
@ -53,58 +54,58 @@ public struct StateData {
|
|||
/// IMK 協定的內文組字區的游標長度與游標位置無法正確統計 UTF8 高萬字(比如 emoji)的長度,
|
||||
/// 所以在這裡必須做糾偏處理。因為在用 Swift,所以可以用「.utf16」取代「NSString.length()」。
|
||||
/// 這樣就可以免除不必要的類型轉換。
|
||||
var u16Cursor: Int {
|
||||
public var u16Cursor: Int {
|
||||
displayedText.charComponents[0..<cursor].joined().utf16.count
|
||||
}
|
||||
|
||||
var u16Marker: Int {
|
||||
public var u16Marker: Int {
|
||||
displayedText.charComponents[0..<marker].joined().utf16.count
|
||||
}
|
||||
|
||||
var u16MarkedRange: Range<Int> {
|
||||
public var u16MarkedRange: Range<Int> {
|
||||
min(u16Cursor, u16Marker)..<max(u16Cursor, u16Marker)
|
||||
}
|
||||
|
||||
// MARK: Other data for non-empty states.
|
||||
|
||||
var isVerticalTyping = false
|
||||
var markedTargetExists: Bool {
|
||||
public var isVerticalTyping = false
|
||||
public var markedTargetExists: Bool {
|
||||
let pair = userPhraseKVPair
|
||||
return LMMgr.checkIfUserPhraseExist(
|
||||
userPhrase: pair.1, mode: IMEApp.currentInputMode, key: pair.0
|
||||
)
|
||||
}
|
||||
|
||||
var displayTextSegments = [String]() {
|
||||
public var displayTextSegments = [String]() {
|
||||
didSet {
|
||||
displayedText = displayTextSegments.joined()
|
||||
}
|
||||
}
|
||||
|
||||
var reading: String = ""
|
||||
var markedReadings = [String]()
|
||||
var candidates = [(String, String)]()
|
||||
var textToCommit: String = ""
|
||||
var tooltip: String = ""
|
||||
var tooltipBackupForInputting: String = ""
|
||||
var attributedStringPlaceholder: NSAttributedString = .init(
|
||||
public var reading: String = ""
|
||||
public var markedReadings = [String]()
|
||||
public var candidates = [(String, String)]()
|
||||
public var textToCommit: String = ""
|
||||
public var tooltip: String = ""
|
||||
public var tooltipBackupForInputting: String = ""
|
||||
public var attributedStringPlaceholder: NSAttributedString = .init(
|
||||
string: " ",
|
||||
attributes: [
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0,
|
||||
]
|
||||
)
|
||||
var isFilterable: Bool {
|
||||
public var isFilterable: Bool {
|
||||
markedTargetExists ? isMarkedLengthValid : false
|
||||
}
|
||||
|
||||
var isMarkedLengthValid: Bool {
|
||||
public var isMarkedLengthValid: Bool {
|
||||
Self.allowedMarkLengthRange.contains(markedRange.count)
|
||||
}
|
||||
|
||||
var tooltipColorState: TooltipUI.ColorStates = .normal
|
||||
public var tooltipColorState: TooltipColorState = .normal
|
||||
|
||||
var attributedStringNormal: NSAttributedString {
|
||||
public var attributedStringNormal: NSAttributedString {
|
||||
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
|
||||
|
@ -122,7 +123,7 @@ public struct StateData {
|
|||
return attributedString
|
||||
}
|
||||
|
||||
var attributedStringMarking: NSAttributedString {
|
||||
public var attributedStringMarking: NSAttributedString {
|
||||
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
|
||||
|
@ -161,7 +162,7 @@ public struct StateData {
|
|||
// MARK: - IMEState 工具函式
|
||||
|
||||
extension StateData {
|
||||
var chkIfUserPhraseExists: Bool {
|
||||
public var doesUserPhraseExist: Bool {
|
||||
let text = displayedText.charComponents[markedRange].joined()
|
||||
let joined = markedReadings.joined(separator: "-")
|
||||
return LMMgr.checkIfUserPhraseExist(
|
||||
|
@ -169,7 +170,7 @@ extension StateData {
|
|||
)
|
||||
}
|
||||
|
||||
var readingThreadForDisplay: String {
|
||||
public var readingThreadForDisplay: String {
|
||||
var arrOutput = [String]()
|
||||
for neta in markedReadings {
|
||||
var neta = neta
|
||||
|
@ -193,19 +194,19 @@ extension StateData {
|
|||
return arrOutput.joined(separator: "\u{A0}")
|
||||
}
|
||||
|
||||
var userPhraseKVPair: (String, String) {
|
||||
public var userPhraseKVPair: (String, String) {
|
||||
let key = markedReadings.joined(separator: "-")
|
||||
let value = displayedText.charComponents[markedRange].joined()
|
||||
return (key, value)
|
||||
}
|
||||
|
||||
var userPhraseDumped: String {
|
||||
public var userPhraseDumped: String {
|
||||
let pair = userPhraseKVPair
|
||||
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||
return "\(pair.1) \(pair.0)\(nerfedScore)"
|
||||
}
|
||||
|
||||
var userPhraseDumpedConverted: String {
|
||||
public var userPhraseDumpedConverted: String {
|
||||
let pair = userPhraseKVPair
|
||||
let text = ChineseConverter.crossConvert(pair.1) ?? ""
|
||||
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||
|
@ -213,7 +214,7 @@ extension StateData {
|
|||
return "\(text) \(pair.0)\(nerfedScore)\t\(convertedMark)"
|
||||
}
|
||||
|
||||
mutating func updateTooltipForMarking() {
|
||||
public mutating func updateTooltipForMarking() {
|
||||
var tooltipForMarking: String {
|
||||
let pair = userPhraseKVPair
|
||||
if PrefMgr.shared.phraseReplacementEnabled {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Shared
|
||||
|
||||
public class ctlPopupCompositionBuffer: NSWindowController {
|
||||
public var isTypingDirectionVertical = false {
|
||||
didSet {
|
||||
|
|
|
@ -32,7 +32,7 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
|||
(inputMode == .imeModeCHT) ? .imeModeCHS : .imeModeCHT
|
||||
if !LMMgr.writeUserPhrase(
|
||||
state.data.userPhraseDumped, inputMode: inputMode,
|
||||
areWeDuplicating: state.data.chkIfUserPhraseExists,
|
||||
areWeDuplicating: state.data.doesUserPhraseExist,
|
||||
areWeDeleting: addToFilter
|
||||
)
|
||||
|| !LMMgr.writeUserPhrase(
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Shared
|
||||
|
||||
// MARK: - 狀態調度 (State Handling)
|
||||
|
||||
extension ctlInputMethod {
|
||||
|
|
Loading…
Reference in New Issue