vChewing-macOS/Packages/vChewing_MainAssembly/Sources/MainAssembly/IMEState.swift

236 lines
9.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// (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 InputMethodKit
import LangModelAssembly
import Shared
/// SessionCtl
///
/// Finite State Machine/
/// 使
/// 使
///
///
/// IMEState
///
///
/// IMEState
/// IMEState.ofMarking IMEState.ofInputting
///
/// (Constructor)
///
///
///
/// - ** .ofDeactivated**: 使使使
/// - ** .ofEmpty**: 使
///
/// /
/// 使
///
/// - ** .ofAssociates**:
/// - ** .ofAbortion**: .ofEmpty()
/// .ofEmpty()
/// - ** .ofCommitting**:
/// .ofEmpty()
/// .ofEmpty()
/// - ** .ofInputting**: 使Compositor
/// - ** .ofMarking**: 使
///
/// - ** .ofCandidates**: 使
/// - ** .ofSymbolTable**:
public struct IMEState: IMEStateProtocol {
public var type: StateType = .ofEmpty
public var data: IMEStateDataProtocol = IMEStateData() as IMEStateDataProtocol
public var node: CandidateNode = .init(name: "")
init(_ data: IMEStateDataProtocol = IMEStateData() as IMEStateDataProtocol, type: StateType = .ofEmpty) {
self.data = data
self.type = type
}
///
/// - Parameters:
/// - displayTextSegments:
/// - cursor: UTF8
fileprivate init(displayTextSegments: [String], cursor: Int) {
// displayTextSegments
data.displayTextSegments = displayTextSegments.map {
if !SessionCtl.isVerticalTyping { return $0 }
guard PrefMgr.shared.hardenVerticalPunctuations else { return $0 }
var neta = $0
ChineseConverter.hardenVerticalPunctuations(target: &neta, convert: SessionCtl.isVerticalTyping)
return neta
}
data.cursor = cursor
data.marker = cursor
}
///
/// - Parameters:
/// - data:
/// - type:
/// - node:
init(
_ data: IMEStateDataProtocol = IMEStateData() as IMEStateDataProtocol, type: StateType = .ofEmpty,
node: CandidateNode
) {
self.data = data
self.type = type
self.node = node
self.data.candidates = node.members.map { ([""], $0.name) }
}
}
// MARK: -
public extension IMEState {
static func ofDeactivated() -> IMEState { .init(type: .ofDeactivated) }
static func ofEmpty() -> IMEState { .init(type: .ofEmpty) }
static func ofAbortion() -> IMEState { .init(type: .ofAbortion) }
///
/// - Remark:
/// `.ofEmpty()` `.ofCommitting()`
/// - Parameter textToCommit:
/// - Returns:
static func ofCommitting(textToCommit: String) -> IMEState {
var result = IMEState(type: .ofCommitting)
result.textToCommit = textToCommit
ChineseConverter.ensureCurrencyNumerals(target: &result.data.textToCommit)
return result
}
static func ofAssociates(candidates: [(keyArray: [String], value: String)]) -> IMEState {
var result = IMEState(type: .ofAssociates)
result.candidates = candidates
return result
}
static func ofInputting(displayTextSegments: [String], cursor: Int, highlightAt highlightAtSegment: Int? = nil) -> IMEState {
var result = IMEState(displayTextSegments: displayTextSegments, cursor: cursor)
result.type = .ofInputting
if let readingAtSegment = highlightAtSegment {
result.data.highlightAtSegment = readingAtSegment
}
return result
}
static func ofMarking(
displayTextSegments: [String], markedReadings: [String], cursor: Int, marker: Int
)
-> IMEState
{
var result = IMEState(displayTextSegments: displayTextSegments, cursor: cursor)
result.type = .ofMarking
result.data.marker = marker
result.data.markedReadings = markedReadings
result.data.updateTooltipForMarking()
return result
}
static func ofCandidates(candidates: [(keyArray: [String], value: String)], displayTextSegments: [String], cursor: Int)
-> IMEState
{
var result = IMEState(displayTextSegments: displayTextSegments, cursor: cursor)
result.type = .ofCandidates
result.data.candidates = candidates
return result
}
static func ofSymbolTable(node: CandidateNode) -> IMEState {
var result = IMEState(node: node)
result.type = .ofSymbolTable
return result
}
}
// MARK: -
public extension IMEState {
var isFilterable: Bool { data.isFilterable }
var markedTargetIsCurrentlyFiltered: Bool { data.markedTargetIsCurrentlyFiltered }
var isMarkedLengthValid: Bool { data.isMarkedLengthValid }
var displayedText: String { data.displayedText }
var displayedTextConverted: String { data.displayedTextConverted }
var displayTextSegments: [String] { data.displayTextSegments }
var markedRange: Range<Int> { data.markedRange }
var u16MarkedRange: Range<Int> { data.u16MarkedRange }
var u16Cursor: Int { data.u16Cursor }
var cursor: Int {
get { data.cursor }
set { data.cursor = newValue }
}
var marker: Int {
get { data.marker }
set { data.marker = newValue }
}
var convertedToInputting: IMEStateProtocol {
if type == .ofInputting { return self }
var result = Self.ofInputting(displayTextSegments: data.displayTextSegments, cursor: data.cursor)
result.tooltip = data.tooltipBackupForInputting
return result
}
var candidates: [(keyArray: [String], value: String)] {
get { data.candidates }
set { data.candidates = newValue }
}
var textToCommit: String {
get { data.textToCommit }
set { data.textToCommit = newValue }
}
var tooltip: String {
get { data.tooltip }
set { data.tooltip = newValue }
}
func attributedString(for session: IMKInputController) -> NSAttributedString {
switch type {
case .ofMarking: return data.attributedStringMarking(for: session)
case .ofCandidates where cursor != marker: return data.attributedStringMarking(for: session)
case .ofCandidates where cursor == marker: break
case .ofAssociates: return data.attributedStringPlaceholder(for: session)
case .ofSymbolTable where displayedText.isEmpty: return data.attributedStringPlaceholder(for: session)
case .ofSymbolTable where !displayedText.isEmpty: break
default: break
}
return data.attributedStringNormal(for: session)
}
/// InputHandler 使 !compositor.isEmpty
var hasComposition: Bool {
switch type {
case .ofInputting, .ofMarking, .ofCandidates: return true
default: return false
}
}
var isCandidateContainer: Bool {
switch type {
case .ofSymbolTable: return !node.members.isEmpty
case .ofCandidates, .ofAssociates, .ofInputting: return !candidates.isEmpty
default: return false
}
}
var tooltipBackupForInputting: String {
get { data.tooltipBackupForInputting }
set { data.tooltipBackupForInputting = newValue }
}
var tooltipDuration: Double {
get { type == .ofMarking ? 0 : data.tooltipDuration }
set { data.tooltipDuration = newValue }
}
}