vChewing-macOS/Source/Modules/IMEStateData.swift

265 lines
8.5 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 Tekkon
public struct StateData {
private static var minCandidateLength: Int {
PrefMgr.shared.allowBoostingSingleKanjiAsUserPhrase ? 1 : 2
}
static var allowedMarkLengthRange: ClosedRange<Int> {
Self.minCandidateLength...PrefMgr.shared.maxCandidateLength
}
var displayedText: String = ""
var displayedTextConverted: String {
///
var result = ChineseConverter.kanjiConversionIfRequired(displayedText)
if result.utf16.count != displayedText.utf16.count
|| result.count != displayedText.count
{
result = displayedText
}
return result
}
// MARK: Cursor & Marker & Range for UTF8
var cursor: Int = 0 {
didSet {
cursor = min(max(cursor, 0), displayedText.count)
}
}
var marker: Int = 0 {
didSet {
marker = min(max(marker, 0), displayedText.count)
}
}
var markedRange: Range<Int> {
min(cursor, marker)..<max(cursor, marker)
}
// MARK: Cursor & Marker & Range for UTF16 (Read-Only)
/// IMK UTF8 emoji
/// Swift.utf16NSString.length()
///
var u16Cursor: Int {
displayedText.charComponents[0..<cursor].joined().utf16.count
}
var u16Marker: Int {
displayedText.charComponents[0..<marker].joined().utf16.count
}
var u16MarkedRange: Range<Int> {
min(u16Cursor, u16Marker)..<max(u16Cursor, u16Marker)
}
// MARK: Other data for non-empty states.
var isVerticalTyping = false
var markedTargetExists: Bool {
let pair = userPhraseKVPair
return LMMgr.checkIfUserPhraseExist(
userPhrase: pair.1, mode: IMEApp.currentInputMode, key: pair.0
)
}
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(
string: " ",
attributes: [
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0,
]
)
var isFilterable: Bool {
markedTargetExists ? isMarkedLengthValid : false
}
var isMarkedLengthValid: Bool {
Self.allowedMarkLengthRange.contains(markedRange.count)
}
var tooltipColorState: ctlTooltip.ColorStates = .normal
var attributedStringNormal: NSAttributedString {
///
/// JIS
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
var newBegin = 0
for (i, neta) in displayTextSegments.enumerated() {
attributedString.setAttributes(
[
/// .thick
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: i,
], range: NSRange(location: newBegin, length: neta.utf16.count)
)
newBegin += neta.utf16.count
}
return attributedString
}
var attributedStringMarking: NSAttributedString {
///
/// JIS
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
let end = u16MarkedRange.upperBound
attributedString.setAttributes(
[
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0,
], range: NSRange(location: 0, length: u16MarkedRange.lowerBound)
)
attributedString.setAttributes(
[
.underlineStyle: NSUnderlineStyle.thick.rawValue,
.markedClauseSegment: 1,
],
range: NSRange(
location: u16MarkedRange.lowerBound,
length: u16MarkedRange.upperBound - u16MarkedRange.lowerBound
)
)
attributedString.setAttributes(
[
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 2,
],
range: NSRange(
location: end,
length: displayedTextConverted.utf16.count - end
)
)
return attributedString
}
}
// MARK: - IMEState
extension StateData {
var chkIfUserPhraseExists: Bool {
let text = displayedText.charComponents[markedRange].joined()
let joined = markedReadings.joined(separator: "-")
return LMMgr.checkIfUserPhraseExist(
userPhrase: text, mode: IMEApp.currentInputMode, key: joined
)
}
var readingThreadForDisplay: String {
var arrOutput = [String]()
for neta in markedReadings {
var neta = neta
if neta.isEmpty { continue }
if neta.contains("_") {
arrOutput.append("??")
continue
}
if PrefMgr.shared.showHanyuPinyinInCompositionBuffer,
PrefMgr.shared.alwaysShowTooltipTextsHorizontally || !isVerticalTyping
{
// ->->調
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: "\u{A0}")
}
var userPhraseKVPair: (String, String) {
let key = markedReadings.joined(separator: "-")
let value = displayedText.charComponents[markedRange].joined()
return (key, value)
}
var userPhraseDumped: String {
let pair = userPhraseKVPair
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
return "\(pair.1) \(pair.0)\(nerfedScore)"
}
var userPhraseDumpedConverted: String {
let pair = userPhraseKVPair
let text = ChineseConverter.crossConvert(pair.1) ?? ""
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
let convertedMark = "#𝙃𝙪𝙢𝙖𝙣𝘾𝙝𝙚𝙘𝙠𝙍𝙚𝙦𝙪𝙞𝙧𝙚𝙙"
return "\(text) \(pair.0)\(nerfedScore)\t\(convertedMark)"
}
mutating func updateTooltipForMarking() {
var tooltipForMarking: String {
let pair = userPhraseKVPair
if PrefMgr.shared.phraseReplacementEnabled {
tooltipColorState = .warning
return NSLocalizedString(
"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: ""
)
}
if markedRange.isEmpty {
return ""
}
let text = pair.1
let readingDisplay = readingThreadForDisplay
if markedRange.count < StateData.allowedMarkLengthRange.lowerBound {
tooltipColorState = .denialInsufficiency
return String(
format: NSLocalizedString(
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""
) + "\n" + readingDisplay, text
)
} else if markedRange.count > StateData.allowedMarkLengthRange.upperBound {
tooltipColorState = .denialOverflow
return String(
format: NSLocalizedString(
"\"%@\" length should ≤ %d for a user phrase.", comment: ""
) + "\n" + readingDisplay, text, StateData.allowedMarkLengthRange.upperBound
)
}
if markedTargetExists {
tooltipColorState = .prompt
return String(
format: NSLocalizedString(
"\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude.",
comment: ""
) + "\n" + readingDisplay, text
)
}
tooltipColorState = .normal
return String(
format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: "") + "\n"
+ readingDisplay,
text
)
}
tooltip = tooltipForMarking
}
}