Repo // Deprecating Zonble's InputState, using IMEState instead.

- This allows adding user phrases with high-unicode chars like emojis.
- AUTHORS // Remove attribution of Zonble's InputState.
This commit is contained in:
ShikiSuen 2022-09-04 09:10:14 +08:00
parent a5fa288f97
commit 5f89fdf9b2
17 changed files with 530 additions and 1158 deletions

View File

@ -15,7 +15,7 @@ $ 3rd-Party Modules Used:
$ Contributors and volunteers of the upstream repo, having no responsibility in discussing anything in the current repo: $ Contributors and volunteers of the upstream repo, having no responsibility in discussing anything in the current repo:
- Zonble Yang: - Zonble Yang:
- McBopomofo for macOS 2.x architect, especially state-based IME behavior management. - McBopomofo for macOS 2.x architect.
- Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen). - Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen).
- Notifier window and Tooltip UI. - Notifier window and Tooltip UI.
- NSStringUtils and FSEventStreamHelper. - NSStringUtils and FSEventStreamHelper.

View File

@ -9,39 +9,83 @@
import Foundation import Foundation
// enum // enum
public enum StateType { public enum StateType: String {
case ofDeactivated case ofDeactivated = "Deactivated"
case ofEmpty case ofEmpty = "Empty"
case ofAbortion // Empty case ofAbortion = "Abortion" // Empty
case ofCommitting case ofCommitting = "Committing"
case ofAssociates case ofAssociates = "Associates"
case ofNotEmpty case ofNotEmpty = "NotEmpty"
case ofInputting case ofInputting = "Inputting"
case ofMarking case ofMarking = "Marking"
case ofCandidates case ofCandidates = "Candidates"
case ofSymbolTable case ofSymbolTable = "SymbolTable"
} }
// InputState // IMEState
public protocol InputStateProtocol { public protocol IMEStateProtocol {
var type: StateType { get } var type: StateType { get }
var data: StateData { get } var data: StateData { get }
var hasBuffer: Bool { get } var candidates: [(String, String)] { get }
var hasComposition: Bool { get }
var isCandidateContainer: Bool { get } var isCandidateContainer: Bool { get }
var displayedText: String { get } var displayedText: String { get }
var textToCommit: String { get set } var textToCommit: String { get set }
var tooltip: String { get set } var tooltip: String { get set }
var attributedString: NSAttributedString { get } var attributedString: NSAttributedString { get }
var convertedToInputting: IMEState { get }
var isFilterable: Bool { get }
var node: SymbolNode { get set } var node: SymbolNode { get set }
} }
public struct IMEState { /// ctlInputMethod
///
/// Finite State Machine/
/// 使
/// 使
///
///
/// IMEState
/// 使
///
///
///
/// IMEState
/// IMEState.Marking IMEState.Inputting
///
/// (Constructor)
///
///
///
/// - .Deactivated: 使使
/// - .AssociatedPhrases:
/// 西 .NotEmpty
/// - .Empty: 使
///
/// - .Abortion: Empty
/// .Empty()
/// - .Committing:
/// - .NotEmpty:
/// - .Inputting: 使Compositor
/// - .Marking: 使
///
/// - .ChoosingCandidate: 使
/// - .SymbolTable:
public struct IMEState: IMEStateProtocol {
public var type: StateType = .ofEmpty public var type: StateType = .ofEmpty
public var data: StateData = .init() public var data: StateData = .init()
public var node: SymbolNode = .init("")
init(_ data: StateData = .init(), type: StateType = .ofEmpty) { init(_ data: StateData = .init(), type: StateType = .ofEmpty) {
self.data = data self.data = data
self.type = type self.type = type
} }
init(_ data: StateData = .init(), type: StateType = .ofEmpty, node: SymbolNode) {
self.data = data
self.type = type
self.node = node
self.data.candidates = { node.children?.map(\.title) ?? [String]() }().map { ("", $0) }
}
} }
// MARK: - // MARK: -
@ -63,59 +107,56 @@ extension IMEState {
return result return result
} }
public static func NotEmpty(nodeValues: [String], reading: String = "", cursor: Int) -> IMEState { public static func NotEmpty(displayTextSegments: [String], cursor: Int) -> IMEState {
var result = IMEState(type: .ofNotEmpty) var result = IMEState(type: .ofNotEmpty)
// nodeValuesArray reading // displayTextSegments
result.data.nodeValuesArray = nodeValues result.data.displayTextSegments = displayTextSegments
if !reading.isEmpty {
result.data.reading = reading // nodeValuesArray
}
// nodeValuesArray 使
result.data.displayedText = result.data.nodeValuesArray.joined()
result.data.cursor = cursor result.data.cursor = cursor
return result return result
} }
public static func Inputting(nodeValues: [String], reading: String = "", cursor: Int) -> IMEState { public static func Inputting(displayTextSegments: [String], cursor: Int) -> IMEState {
var result = IMEState.NotEmpty(nodeValues: nodeValues, reading: reading, cursor: cursor) var result = IMEState.NotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
result.type = .ofInputting result.type = .ofInputting
return result return result
} }
public static func Marking(nodeValues: [String], nodeReadings: [String], cursor: Int, marker: Int) -> IMEState { public static func Marking(
var result = IMEState.NotEmpty(nodeValues: nodeValues, cursor: cursor) displayTextSegments: [String], markedReadings: [String], cursor: Int, marker: Int
)
-> IMEState
{
var result = IMEState.NotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
result.type = .ofMarking result.type = .ofMarking
result.data.nodeReadingsArray = nodeReadings
result.data.marker = marker result.data.marker = marker
result.data.markedReadings = markedReadings
StateData.Marking.updateParameters(&result.data) StateData.Marking.updateParameters(&result.data)
return result return result
} }
public static func Candidates(candidates: [(String, String)], nodeValues: [String], cursor: Int) -> IMEState { public static func Candidates(candidates: [(String, String)], displayTextSegments: [String], cursor: Int) -> IMEState
var result = IMEState.NotEmpty(nodeValues: nodeValues, cursor: cursor) {
var result = IMEState.NotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
result.type = .ofCandidates result.type = .ofCandidates
result.data.candidates = candidates result.data.candidates = candidates
return result return result
} }
public static func SymbolTable(node: SymbolNode, previous: SymbolNode? = nil) -> IMEState { public static func SymbolTable(node: SymbolNode) -> IMEState {
let candidates = { node.children?.map(\.title) ?? [String]() }().map { ("", $0) } var result = IMEState(type: .ofNotEmpty, node: node)
var result = IMEState.Candidates(candidates: candidates, nodeValues: [], cursor: 0)
result.type = .ofSymbolTable result.type = .ofSymbolTable
result.data.node = node
if let previous = previous {
result.data.node.previous = previous
}
return result return result
} }
} }
// MARK: - // MARK: -
extension IMEState: InputStateProtocol { extension IMEState {
public var isFilterable: Bool { data.isFilterable }
public var candidates: [(String, String)] { data.candidates }
public var convertedToInputting: IMEState { public var convertedToInputting: IMEState {
if type == .ofInputting { return self } if type == .ofInputting { return self }
var result = IMEState.Inputting(nodeValues: data.nodeValuesArray, reading: data.reading, cursor: data.cursor) var result = IMEState.Inputting(displayTextSegments: data.displayTextSegments, cursor: data.cursor)
result.tooltip = data.tooltipBackupForInputting result.tooltip = data.tooltipBackupForInputting
return result return result
} }
@ -146,25 +187,7 @@ extension IMEState: InputStateProtocol {
} }
} }
public var node: SymbolNode { public var hasComposition: Bool {
get {
data.node
}
set {
data.node = newValue
}
}
public var tooltipBackupForInputting: String {
get {
data.tooltipBackupForInputting
}
set {
data.tooltipBackupForInputting = newValue
}
}
public var hasBuffer: Bool {
switch type { switch type {
case .ofNotEmpty, .ofInputting, .ofMarking, .ofCandidates: return true case .ofNotEmpty, .ofInputting, .ofMarking, .ofCandidates: return true
default: return false default: return false

View File

@ -38,6 +38,9 @@ public struct StateData {
// MARK: Cursor & Marker & Range for UTF16 (Read-Only) // MARK: Cursor & Marker & Range for UTF16 (Read-Only)
/// IMK UTF8 emoji
/// Swift.utf16NSString.length()
///
var u16Cursor: Int { var u16Cursor: Int {
displayedText.charComponents[0..<cursor].joined().utf16.count displayedText.charComponents[0..<cursor].joined().utf16.count
} }
@ -53,32 +56,14 @@ public struct StateData {
// MARK: Other data for non-empty states. // MARK: Other data for non-empty states.
var markedTargetExists: Bool = false var markedTargetExists: Bool = false
var nodeReadingsArray = [String]() var displayTextSegments = [String]() {
var nodeValuesArray = [String]()
var reading: String = "" {
didSet { didSet {
if !reading.isEmpty { displayedText = displayTextSegments.joined()
var newNodeValuesArray = [String]()
var temporaryNode = ""
var charCounter = 0
for node in nodeValuesArray {
for char in node {
if charCounter == cursor - reading.count {
newNodeValuesArray.append(temporaryNode)
temporaryNode = ""
newNodeValuesArray.append(reading)
}
temporaryNode += String(char)
charCounter += 1
}
newNodeValuesArray.append(temporaryNode)
temporaryNode = ""
}
nodeValuesArray = newNodeValuesArray.isEmpty ? [reading] : newNodeValuesArray
}
} }
} }
var reading: String = ""
var markedReadings = [String]()
var candidates = [(String, String)]() var candidates = [(String, String)]()
var textToCommit: String = "" var textToCommit: String = ""
var tooltip: String = "" var tooltip: String = ""
@ -94,13 +79,12 @@ public struct StateData {
markedTargetExists ? mgrPrefs.allowedMarkRange.contains(markedRange.count) : false markedTargetExists ? mgrPrefs.allowedMarkRange.contains(markedRange.count) : false
} }
var readingCountMismatched: Bool { displayedText.count != nodeReadingsArray.count }
var attributedStringNormal: NSAttributedString { var attributedStringNormal: NSAttributedString {
/// ///
/// JIS /// JIS
let attributedString = NSMutableAttributedString(string: displayedText) let attributedString = NSMutableAttributedString(string: displayedText)
var newBegin = 0 var newBegin = 0
for (i, neta) in nodeValuesArray.enumerated() { for (i, neta) in displayTextSegments.enumerated() {
attributedString.setAttributes( attributedString.setAttributes(
[ [
/// .thick /// .thick
@ -147,17 +131,14 @@ public struct StateData {
) )
return attributedString return attributedString
} }
var node: SymbolNode = .init("")
} }
// MARK: - InputState // MARK: - IMEState
extension StateData { extension StateData {
var chkIfUserPhraseExists: Bool { var chkIfUserPhraseExists: Bool {
let text = displayedText.charComponents[markedRange].joined() let text = displayedText.charComponents[markedRange].joined()
let selectedReadings = nodeReadingsArray[markedRange] let joined = markedReadings.joined(separator: "-")
let joined = selectedReadings.joined(separator: "-")
return mgrLangModel.checkIfUserPhraseExist( return mgrLangModel.checkIfUserPhraseExist(
userPhrase: text, mode: IME.currentInputMode, key: joined userPhrase: text, mode: IME.currentInputMode, key: joined
) )
@ -165,8 +146,7 @@ extension StateData {
var userPhrase: String { var userPhrase: String {
let text = displayedText.charComponents[markedRange].joined() let text = displayedText.charComponents[markedRange].joined()
let selectedReadings = nodeReadingsArray[markedRange] let joined = markedReadings.joined(separator: "-")
let joined = selectedReadings.joined(separator: "-")
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : "" let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
return "\(text) \(joined)\(nerfedScore)" return "\(text) \(joined)\(nerfedScore)"
} }
@ -174,8 +154,7 @@ extension StateData {
var userPhraseConverted: String { var userPhraseConverted: String {
let text = let text =
ChineseConverter.crossConvert(displayedText.charComponents[markedRange].joined()) ?? "" ChineseConverter.crossConvert(displayedText.charComponents[markedRange].joined()) ?? ""
let selectedReadings = nodeReadingsArray[markedRange] let joined = markedReadings.joined(separator: "-")
let joined = selectedReadings.joined(separator: "-")
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : "" let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
let convertedMark = "#𝙃𝙪𝙢𝙖𝙣𝘾𝙝𝙚𝙘𝙠𝙍𝙚𝙦𝙪𝙞𝙧𝙚𝙙" let convertedMark = "#𝙃𝙪𝙢𝙖𝙣𝘾𝙝𝙚𝙘𝙠𝙍𝙚𝙦𝙪𝙞𝙧𝙚𝙙"
return "\(text) \(joined)\(nerfedScore)\t\(convertedMark)" return "\(text) \(joined)\(nerfedScore)\t\(convertedMark)"
@ -184,7 +163,7 @@ extension StateData {
enum Marking { enum Marking {
private static func generateReadingThread(_ data: StateData) -> String { private static func generateReadingThread(_ data: StateData) -> String {
var arrOutput = [String]() var arrOutput = [String]()
for neta in data.nodeReadingsArray[data.markedRange] { for neta in data.markedReadings {
var neta = neta var neta = neta
if neta.isEmpty { continue } if neta.isEmpty { continue }
if neta.contains("_") { if neta.contains("_") {
@ -207,12 +186,6 @@ extension StateData {
/// - Parameter data: /// - Parameter data:
public static func updateParameters(_ data: inout StateData) { public static func updateParameters(_ data: inout StateData) {
var tooltipGenerated: String { var tooltipGenerated: String {
if data.displayedText.count != data.nodeReadingsArray.count {
ctlInputMethod.tooltipController.setColor(state: .redAlert)
return NSLocalizedString(
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: ""
)
}
if mgrPrefs.phraseReplacementEnabled { if mgrPrefs.phraseReplacementEnabled {
ctlInputMethod.tooltipController.setColor(state: .warning) ctlInputMethod.tooltipController.setColor(state: .warning)
return NSLocalizedString( return NSLocalizedString(
@ -240,8 +213,7 @@ extension StateData {
) )
} }
let selectedReadings = data.nodeReadingsArray[data.markedRange] let joined = data.markedReadings.joined(separator: "-")
let joined = selectedReadings.joined(separator: "-")
let exist = mgrLangModel.checkIfUserPhraseExist( let exist = mgrLangModel.checkIfUserPhraseExist(
userPhrase: text, mode: IME.currentInputMode, key: joined userPhrase: text, mode: IME.currentInputMode, key: joined
) )

View File

@ -1,488 +0,0 @@
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are of:
// (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 Foundation
// InputState 使 Struct Struct
/// ctlInputMethod
///
/// Finite State Machine/
/// 使
/// 使
///
///
/// InputState
/// 使
///
///
///
/// InputState
/// InputState.Marking InputState.Inputting
///
///
///
///
/// - .Deactivated: 使使
/// - .AssociatedPhrases:
/// 西 .NotEmpty
/// - .Empty: 使
///
/// - .Abortion: Empty
/// .Empty()
/// - .Committing:
/// - .NotEmpty:
/// - .Inputting: 使Compositor
/// - .Marking: 使
///
/// - .ChoosingCandidate: 使
/// - .SymbolTable:
public enum InputState {
/// .Deactivated: 使使
class Deactivated: InputStateProtocol {
var node: SymbolNode = .init("")
var attributedString: NSAttributedString = .init()
var data: StateData = .init()
var textToCommit: String = ""
var tooltip: String = ""
let displayedText: String = ""
let hasBuffer: Bool = false
let isCandidateContainer: Bool = false
public var type: StateType { .ofDeactivated }
}
// MARK: -
/// .Empty: 使
///
class Empty: InputStateProtocol {
var node: SymbolNode = .init("")
var attributedString: NSAttributedString = .init()
var data: StateData = .init()
var textToCommit: String = ""
var tooltip: String = ""
let hasBuffer: Bool = false
let isCandidateContainer: Bool = false
public var type: StateType { .ofEmpty }
let displayedText: String = ""
}
// MARK: -
/// .Abortion: Empty
///
/// .Empty()
class Abortion: Empty {
override public var type: StateType { .ofAbortion }
}
// MARK: -
/// .Committing:
class Committing: InputStateProtocol {
var node: SymbolNode = .init("")
var attributedString: NSAttributedString = .init()
var data: StateData = .init()
var tooltip: String = ""
var textToCommit: String = ""
let displayedText: String = ""
let hasBuffer: Bool = false
let isCandidateContainer: Bool = false
public var type: StateType { .ofCommitting }
init(textToCommit: String) {
self.textToCommit = textToCommit
ChineseConverter.ensureCurrencyNumerals(target: &self.textToCommit)
}
}
// MARK: -
/// .AssociatedPhrases:
/// 西 .NotEmpty
class Associates: InputStateProtocol {
var node: SymbolNode = .init("")
var attributedString: NSAttributedString = .init()
var data: StateData = .init()
var textToCommit: String = ""
var tooltip: String = ""
let displayedText: String = ""
let hasBuffer: Bool = false
let isCandidateContainer: Bool = true
public var type: StateType { .ofAssociates }
var candidates: [(String, String)] { data.candidates }
init(candidates: [(String, String)]) {
data.candidates = candidates
attributedString = {
let attributedString = NSMutableAttributedString(
string: " ",
attributes: [
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0,
]
)
return attributedString
}()
}
}
// MARK: -
/// .NotEmpty:
/// - .Inputting: 使Compositor
/// - .Marking: 使
///
/// - .ChoosingCandidate: 使
/// - .SymbolTable:
class NotEmpty: InputStateProtocol {
var node: SymbolNode = .init("")
var attributedString: NSAttributedString = .init()
var data: StateData = .init()
var tooltip: String = ""
var textToCommit: String = ""
let hasBuffer: Bool = true
var isCandidateContainer: Bool { false }
public var type: StateType { .ofNotEmpty }
private(set) var displayedText: String
private(set) var cursorIndex: Int = 0 { didSet { cursorIndex = max(cursorIndex, 0) } }
private(set) var reading: String = ""
private(set) var nodeValuesArray = [String]()
public var displayedTextConverted: String {
let converted = IME.kanjiConversionIfRequired(displayedText)
if converted.utf16.count != displayedText.utf16.count
|| converted.count != displayedText.count
{
return displayedText
}
return converted
}
public var committingBufferConverted: String { displayedTextConverted }
init(displayedText: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) {
self.displayedText = displayedText
self.reading = reading
// reading
if !reading.isEmpty {
var newNodeValuesArray = [String]()
var temporaryNode = ""
var charCounter = 0
for node in nodeValuesArray {
for char in node {
if charCounter == cursorIndex - reading.utf16.count {
newNodeValuesArray.append(temporaryNode)
temporaryNode = ""
newNodeValuesArray.append(reading)
}
temporaryNode += String(char)
charCounter += 1
}
newNodeValuesArray.append(temporaryNode)
temporaryNode = ""
}
self.nodeValuesArray = newNodeValuesArray
} else {
self.nodeValuesArray = nodeValuesArray
}
defer {
self.cursorIndex = cursorIndex
self.attributedString = {
///
/// JIS
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
var newBegin = 0
for (i, neta) in nodeValuesArray.enumerated() {
attributedString.setAttributes(
[
/// .thick
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: i,
], range: NSRange(location: newBegin, length: neta.utf16.count)
)
newBegin += neta.utf16.count
}
return attributedString
}()
}
}
}
// MARK: -
/// .Inputting: 使Compositor
class Inputting: NotEmpty {
override public var type: StateType { .ofInputting }
override public var committingBufferConverted: String {
let committingBuffer = nodeValuesArray.joined()
let converted = IME.kanjiConversionIfRequired(committingBuffer)
if converted.utf16.count != displayedText.utf16.count
|| converted.count != displayedText.count
{
return displayedText
}
return converted
}
override init(displayedText: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) {
super.init(
displayedText: displayedText, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
)
}
}
// MARK: -
/// .Marking: 使
///
class Marking: NotEmpty {
override public var type: StateType { .ofMarking }
private var allowedMarkRange: ClosedRange<Int> = mgrPrefs.minCandidateLength...mgrPrefs.maxCandidateLength
private(set) var markerIndex: Int = 0 { didSet { markerIndex = max(markerIndex, 0) } }
private(set) var markedRange: Range<Int>
private var literalMarkedRange: Range<Int> {
let lowerBoundLiteral = displayedText.charIndexLiteral(from: markedRange.lowerBound)
let upperBoundLiteral = displayedText.charIndexLiteral(from: markedRange.upperBound)
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 markedTargetExists = false
var tooltipForMarking: String {
if displayedText.count != readings.count {
ctlInputMethod.tooltipController.setColor(state: .redAlert)
return NSLocalizedString(
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: ""
)
}
if mgrPrefs.phraseReplacementEnabled {
ctlInputMethod.tooltipController.setColor(state: .warning)
return NSLocalizedString(
"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: ""
)
}
if markedRange.isEmpty {
return ""
}
let text = displayedText.utf16SubString(with: markedRange)
if literalMarkedRange.count < allowedMarkRange.lowerBound {
ctlInputMethod.tooltipController.setColor(state: .denialInsufficiency)
return String(
format: NSLocalizedString(
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""
) + "\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: ""
) + "\n// " + literalReadingThread, text, allowedMarkRange.upperBound
)
}
let selectedReadings = readings[literalMarkedRange]
let joined = selectedReadings.joined(separator: "-")
let exist = mgrLangModel.checkIfUserPhraseExist(
userPhrase: text, mode: IME.currentInputMode, key: joined
)
if exist {
markedTargetExists = exist
ctlInputMethod.tooltipController.setColor(state: .prompt)
return String(
format: NSLocalizedString(
"\"%@\" already exists: ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude.",
comment: ""
) + "\n// " + literalReadingThread, text
)
}
ctlInputMethod.tooltipController.resetColor()
return String(
format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: "") + "\n// "
+ literalReadingThread,
text
)
}
var tooltipBackupForInputting: String = ""
private(set) var readings: [String]
init(
displayedText: String, cursorIndex: Int, markerIndex: Int, readings: [String], nodeValuesArray: [String] = []
) {
let begin = min(cursorIndex, markerIndex)
let end = max(cursorIndex, markerIndex)
markedRange = begin..<end
self.readings = readings
super.init(
displayedText: displayedText, cursorIndex: cursorIndex, nodeValuesArray: nodeValuesArray
)
defer {
self.markerIndex = markerIndex
tooltip = tooltipForMarking
attributedString = {
///
/// JIS
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
let end = markedRange.upperBound
attributedString.setAttributes(
[
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0,
], range: NSRange(location: 0, length: markedRange.lowerBound)
)
attributedString.setAttributes(
[
.underlineStyle: NSUnderlineStyle.thick.rawValue,
.markedClauseSegment: 1,
],
range: NSRange(
location: markedRange.lowerBound,
length: markedRange.upperBound - markedRange.lowerBound
)
)
attributedString.setAttributes(
[
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 2,
],
range: NSRange(
location: end,
length: displayedText.utf16.count - end
)
)
return attributedString
}()
}
}
var convertedToInputting: Inputting {
let state = Inputting(
displayedText: displayedText, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
)
state.tooltip = tooltipBackupForInputting
return state
}
var isFilterable: Bool { markedTargetExists ? allowedMarkRange.contains(literalMarkedRange.count) : false }
var bufferReadingCountMisMatch: Bool { displayedText.count != readings.count }
var chkIfUserPhraseExists: Bool {
let text = displayedText.utf16SubString(with: markedRange)
let selectedReadings = readings[literalMarkedRange]
let joined = selectedReadings.joined(separator: "-")
return mgrLangModel.checkIfUserPhraseExist(
userPhrase: text, mode: IME.currentInputMode, key: joined
)
}
var userPhrase: String {
let text = displayedText.utf16SubString(with: markedRange)
let selectedReadings = readings[literalMarkedRange]
let joined = selectedReadings.joined(separator: "-")
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
return "\(text) \(joined)\(nerfedScore)"
}
var userPhraseConverted: String {
let text =
ChineseConverter.crossConvert(displayedText.utf16SubString(with: markedRange)) ?? ""
let selectedReadings = readings[literalMarkedRange]
let joined = selectedReadings.joined(separator: "-")
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
let convertedMark = "#𝙃𝙪𝙢𝙖𝙣𝘾𝙝𝙚𝙘𝙠𝙍𝙚𝙦𝙪𝙞𝙧𝙚𝙙"
return "\(text) \(joined)\(nerfedScore)\t\(convertedMark)"
}
}
// MARK: -
/// .ChoosingCandidate: 使
class ChoosingCandidate: NotEmpty {
override var isCandidateContainer: Bool { true }
override public var type: StateType { .ofCandidates }
var candidates: [(String, String)]
// ctlInputMethod.candidateSelectionChanged()
public var chosenCandidateString: String = "" {
didSet {
// / JIS
if chosenCandidateString.contains("\u{17}") {
chosenCandidateString = String(chosenCandidateString.split(separator: "\u{17}")[0])
}
if !chosenCandidateString.contains("\u{1A}") { return }
chosenCandidateString = String(chosenCandidateString.split(separator: "\u{1A}").reversed()[0])
}
}
init(
displayedText: String, cursorIndex: Int, candidates: [(String, String)],
nodeValuesArray: [String] = []
) {
self.candidates = candidates
super.init(displayedText: displayedText, cursorIndex: cursorIndex, nodeValuesArray: nodeValuesArray)
}
}
// MARK: -
/// .SymbolTable:
class SymbolTable: ChoosingCandidate {
override public var type: StateType { .ofSymbolTable }
init(node: SymbolNode, previous: SymbolNode? = nil) {
super.init(displayedText: "", cursorIndex: 0, candidates: [])
self.node = node
if let previous = previous {
self.node.previous = previous
}
let candidates = node.children?.map(\.title) ?? [String]()
self.candidates = candidates.map { ("", $0) }
// InputState.SymbolTable
// MS Word
//
//
// Crediting Qwertyyb: https://github.com/qwertyyb/Fire/issues/55#issuecomment-1133497700
attributedString = {
let attributedString = NSMutableAttributedString(
string: " ",
attributes: [
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0,
]
)
return attributedString
}()
}
}
}

View File

@ -25,7 +25,7 @@ protocol KeyHandlerDelegate {
_: KeyHandler, didSelectCandidateAt index: Int, _: KeyHandler, didSelectCandidateAt index: Int,
ctlCandidate controller: ctlCandidateProtocol ctlCandidate controller: ctlCandidateProtocol
) )
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol, addToFilter: Bool) func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: IMEStateProtocol, addToFilter: Bool)
-> Bool -> Bool
} }
@ -35,8 +35,6 @@ protocol KeyHandlerDelegate {
public class KeyHandler { public class KeyHandler {
/// ///
let kEpsilon: Double = 0.000001 let kEpsilon: Double = 0.000001
///
var isCursorCuttingChar = false
/// ///
var isTypingContentEmpty: Bool { composer.isEmpty && compositor.isEmpty } var isTypingContentEmpty: Bool { composer.isEmpty && compositor.isEmpty }
@ -88,6 +86,21 @@ public class KeyHandler {
// MARK: - Functions dealing with Megrez. // MARK: - Functions dealing with Megrez.
///
/// - Returns:
func currentMarkedRange() -> Range<Int> {
min(compositor.cursor, compositor.marker)..<max(compositor.cursor, compositor.marker)
}
///
func isCursorCuttingChar(isMarker: Bool = false) -> Bool {
let index = isMarker ? compositor.marker : compositor.cursor
var isBound = (index == compositor.walkedNodes.contextRange(ofGivenCursor: index).lowerBound)
if index == compositor.width { isBound = true }
let rawResult = compositor.walkedNodes.findNode(at: index)?.isReadingMismatched ?? false
return !isBound && rawResult
}
/// Megrez 使便 /// Megrez 使便
/// ///
/// 使 Node Crossing /// 使 Node Crossing
@ -107,7 +120,8 @@ public class KeyHandler {
// GraphViz // GraphViz
if mgrPrefs.isDebugModeEnabled { if mgrPrefs.isDebugModeEnabled {
let result = compositor.dumpDOT let result = compositor.dumpDOT
let appSupportPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].path.appending("vChewing-visualization.dot") let appSupportPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].path.appending(
"vChewing-visualization.dot")
do { do {
try result.write(toFile: appSupportPath, atomically: true, encoding: .utf8) try result.write(toFile: appSupportPath, atomically: true, encoding: .utf8)
} catch { } catch {

View File

@ -23,9 +23,9 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: IMK /// - Returns: IMK
func handleCandidate( func handleCandidate(
state: InputStateProtocol, state: IMEStateProtocol,
input: InputSignalProtocol, input: InputSignalProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard var ctlCandidateCurrent = delegate?.ctlCandidate() else { guard var ctlCandidateCurrent = delegate?.ctlCandidate() else {
@ -41,7 +41,7 @@ extension KeyHandler {
|| ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold) || ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold)
if cancelCandidateKey { if cancelCandidateKey {
if state is InputState.Associates if state.type == .ofAssociates
|| mgrPrefs.useSCPCTypingMode || mgrPrefs.useSCPCTypingMode
|| compositor.isEmpty || compositor.isEmpty
{ {
@ -49,13 +49,12 @@ extension KeyHandler {
// //
// 使 BackSpace // 使 BackSpace
// compositor.isEmpty // compositor.isEmpty
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
} else { } else {
stateCallback(buildInputtingState) stateCallback(buildInputtingState)
} }
if let state = state as? InputState.SymbolTable, let nodePrevious = state.node.previous { if state.type == .ofSymbolTable, let nodePrevious = state.node.previous, let _ = nodePrevious.children {
stateCallback(InputState.SymbolTable(node: nodePrevious)) stateCallback(IMEState.SymbolTable(node: nodePrevious))
} }
return true return true
} }
@ -63,9 +62,8 @@ extension KeyHandler {
// MARK: Enter // MARK: Enter
if input.isEnter { if input.isEnter {
if state is InputState.Associates, !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter { if state.type == .ofAssociates, !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter {
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
return true return true
} }
delegate?.keyHandler( delegate?.keyHandler(
@ -243,23 +241,15 @@ extension KeyHandler {
// MARK: End Key // MARK: End Key
var candidates: [(String, String)]! if state.candidates.isEmpty {
if let state = state as? InputState.ChoosingCandidate {
candidates = state.candidates
} else if let state = state as? InputState.Associates {
candidates = state.candidates
}
if candidates.isEmpty {
return false return false
} else { // count > 0!isEmpty滿 } else { // count > 0!isEmpty滿
if input.isEnd || input.emacsKey == EmacsKey.end { if input.isEnd || input.emacsKey == EmacsKey.end {
if ctlCandidateCurrent.selectedCandidateIndex == candidates.count - 1 { if ctlCandidateCurrent.selectedCandidateIndex == state.candidates.count - 1 {
IME.prtDebugIntel("9B69AAAD") IME.prtDebugIntel("9B69AAAD")
errorCallback() errorCallback()
} else { } else {
ctlCandidateCurrent.selectedCandidateIndex = candidates.count - 1 ctlCandidateCurrent.selectedCandidateIndex = state.candidates.count - 1
} }
return true return true
} }
@ -267,13 +257,13 @@ extension KeyHandler {
// MARK: (Associated Phrases) // MARK: (Associated Phrases)
if state is InputState.Associates { if state.type == .ofAssociates {
if !input.isShiftHold { return false } if !input.isShiftHold { return false }
} }
var index: Int = NSNotFound var index: Int = NSNotFound
let match: String = let match: String =
(state is InputState.Associates) ? input.inputTextIgnoringModifiers ?? "" : input.text (state.type == .ofAssociates) ? input.inputTextIgnoringModifiers ?? "" : input.text
for j in 0..<ctlCandidateCurrent.keyLabels.count { for j in 0..<ctlCandidateCurrent.keyLabels.count {
let label: CandidateKeyLabel = ctlCandidateCurrent.keyLabels[j] let label: CandidateKeyLabel = ctlCandidateCurrent.keyLabels[j]
@ -293,7 +283,7 @@ extension KeyHandler {
} }
} }
if state is InputState.Associates { return false } if state.type == .ofAssociates { return false }
// MARK: (SCPC Mode Processing) // MARK: (SCPC Mode Processing)
@ -333,10 +323,9 @@ extension KeyHandler {
didSelectCandidateAt: candidateIndex, didSelectCandidateAt: candidateIndex,
ctlCandidate: ctlCandidateCurrent ctlCandidate: ctlCandidateCurrent
) )
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
return handle( return handle(
input: input, state: InputState.Empty(), stateCallback: stateCallback, errorCallback: errorCallback input: input, state: IMEState.Empty(), stateCallback: stateCallback, errorCallback: errorCallback
) )
} }
return true return true

View File

@ -19,7 +19,7 @@ extension KeyHandler {
/// - Returns: IMK /// - Returns: IMK
func handleComposition( func handleComposition(
input: InputSignalProtocol, input: InputSignalProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool? { ) -> Bool? {
// MARK: (Handle BPMF Keys) // MARK: (Handle BPMF Keys)
@ -100,8 +100,7 @@ extension KeyHandler {
switch compositor.isEmpty { switch compositor.isEmpty {
case false: stateCallback(buildInputtingState) case false: stateCallback(buildInputtingState)
case true: case true:
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
} }
return true // IMK return true // IMK
} }
@ -124,31 +123,26 @@ extension KeyHandler {
/// ///
if mgrPrefs.useSCPCTypingMode { if mgrPrefs.useSCPCTypingMode {
let choosingCandidates: InputState.ChoosingCandidate = buildCandidate( let candidateState: IMEState = buildCandidate(
state: inputting, state: inputting,
isTypingVertical: input.isTypingVertical isTypingVertical: input.isTypingVertical
) )
if choosingCandidates.candidates.count == 1, let firstCandidate = choosingCandidates.candidates.first { if candidateState.candidates.count == 1, let firstCandidate = candidateState.candidates.first {
let reading: String = firstCandidate.0 let reading: String = firstCandidate.0
let text: String = firstCandidate.1 let text: String = firstCandidate.1
stateCallback(InputState.Committing(textToCommit: text)) stateCallback(IMEState.Committing(textToCommit: text))
if !mgrPrefs.associatedPhrasesEnabled { if !mgrPrefs.associatedPhrasesEnabled {
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
} else { } else {
if let associatedPhrases = let associatedPhrases =
buildAssociatePhraseState( buildAssociatePhraseState(
withPair: .init(key: reading, value: text), withPair: .init(key: reading, value: text)
isTypingVertical: input.isTypingVertical )
), !associatedPhrases.candidates.isEmpty stateCallback(associatedPhrases.candidates.isEmpty ? IMEState.Empty() : associatedPhrases)
{
stateCallback(associatedPhrases)
} else {
stateCallback(InputState.Empty())
}
} }
} else { } else {
stateCallback(choosingCandidates) stateCallback(candidateState)
} }
} }
// ctlInputMethod IMK // ctlInputMethod IMK

View File

@ -25,8 +25,8 @@ extension KeyHandler {
/// - Returns: IMK /// - Returns: IMK
func handle( func handle(
input: InputSignalProtocol, input: InputSignalProtocol,
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
// inputTest // inputTest
@ -39,7 +39,7 @@ extension KeyHandler {
if input.isInvalid { if input.isInvalid {
// .Empty(IgnoringPreviousState) .Deactivated // .Empty(IgnoringPreviousState) .Deactivated
// .Abortion.Empty // .Abortion.Empty
if state is InputState.Empty || state is InputState.Deactivated { if state.type == .ofEmpty || state.type == .ofDeactivated {
return false return false
} }
IME.prtDebugIntel("550BCF7B: KeyHandler just refused an invalid input.") IME.prtDebugIntel("550BCF7B: KeyHandler just refused an invalid input.")
@ -51,7 +51,7 @@ extension KeyHandler {
// //
let isFunctionKey: Bool = let isFunctionKey: Bool =
input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNonLaptopFunctionKey) input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNonLaptopFunctionKey)
if !(state is InputState.NotEmpty) && !(state is InputState.Associates) && isFunctionKey { if state.type != .ofAssociates, !state.hasComposition, !state.isCandidateContainer, isFunctionKey {
return false return false
} }
@ -68,7 +68,7 @@ extension KeyHandler {
// BackSpace // BackSpace
} else if input.isCapsLockOn || input.isASCIIModeInput { } else if input.isCapsLockOn || input.isASCIIModeInput {
// //
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
// Shift // Shift
if input.isUpperCaseASCIILetterKey { if input.isUpperCaseASCIILetterKey {
@ -82,8 +82,8 @@ extension KeyHandler {
} }
// //
stateCallback(InputState.Committing(textToCommit: inputText.lowercased())) stateCallback(IMEState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
} }
@ -94,19 +94,19 @@ extension KeyHandler {
// 使 Cocoa flags // 使 Cocoa flags
// //
if input.isNumericPadKey { if input.isNumericPadKey {
if !(state is InputState.ChoosingCandidate || state is InputState.Associates if !(state.type == .ofCandidates || state.type == .ofAssociates
|| state is InputState.SymbolTable) || state.type == .ofSymbolTable)
{ {
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
stateCallback(InputState.Committing(textToCommit: inputText.lowercased())) stateCallback(IMEState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
} }
} }
// MARK: (Handle Candidates) // MARK: (Handle Candidates)
if state is InputState.ChoosingCandidate { if [.ofCandidates, .ofSymbolTable].contains(state.type) {
return handleCandidate( return handleCandidate(
state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
) )
@ -114,26 +114,26 @@ extension KeyHandler {
// MARK: (Handle Associated Phrases) // MARK: (Handle Associated Phrases)
if state is InputState.Associates { if state.type == .ofAssociates {
if handleCandidate( if handleCandidate(
state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
) { ) {
return true return true
} else { } else {
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
} }
} }
// MARK: 便使() (Handle Marking) // MARK: 便使() (Handle Marking)
if let marking = state as? InputState.Marking { if state.type == .ofMarking {
if handleMarkingState( if handleMarkingState(
marking, input: input, stateCallback: stateCallback, state, input: input, stateCallback: stateCallback,
errorCallback: errorCallback errorCallback: errorCallback
) { ) {
return true return true
} }
state = marking.convertedToInputting state = state.convertedToInputting
stateCallback(state) stateCallback(state)
} }
@ -147,7 +147,7 @@ extension KeyHandler {
// MARK: (Calling candidate window using Up / Down or PageUp / PageDn.) // MARK: (Calling candidate window using Up / Down or PageUp / PageDn.)
if let currentState = state as? InputState.NotEmpty, composer.isEmpty, !input.isOptionHold, if state.hasComposition, composer.isEmpty, !input.isOptionHold,
input.isCursorClockLeft || input.isCursorClockRight || input.isSpace input.isCursorClockLeft || input.isCursorClockRight || input.isSpace
|| input.isPageDown || input.isPageUp || (input.isTab && mgrPrefs.specifyShiftTabKeyBehavior) || input.isPageDown || input.isPageUp || (input.isTab && mgrPrefs.specifyShiftTabKeyBehavior)
{ {
@ -155,12 +155,12 @@ extension KeyHandler {
/// Space /// Space
if !mgrPrefs.chooseCandidateUsingSpace { if !mgrPrefs.chooseCandidateUsingSpace {
if compositor.cursor >= compositor.length { if compositor.cursor >= compositor.length {
let displayedText = currentState.displayedText let displayedText = state.displayedText
if !displayedText.isEmpty { if !displayedText.isEmpty {
stateCallback(InputState.Committing(textToCommit: displayedText)) stateCallback(IMEState.Committing(textToCommit: displayedText))
} }
stateCallback(InputState.Committing(textToCommit: " ")) stateCallback(IMEState.Committing(textToCommit: " "))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
} else if currentLM.hasUnigramsFor(key: " ") { } else if currentLM.hasUnigramsFor(key: " ") {
compositor.insertKey(" ") compositor.insertKey(" ")
walk() walk()
@ -175,7 +175,7 @@ extension KeyHandler {
) )
} }
} }
stateCallback(buildCandidate(state: currentState, isTypingVertical: input.isTypingVertical)) stateCallback(buildCandidate(state: state))
return true return true
} }
@ -236,7 +236,7 @@ extension KeyHandler {
// MARK: Clock-Left & Clock-Right // MARK: Clock-Left & Clock-Right
if input.isCursorClockLeft || input.isCursorClockRight { if input.isCursorClockLeft || input.isCursorClockRight {
if input.isOptionHold, state is InputState.Inputting { if input.isOptionHold, state.type == .ofInputting {
if input.isCursorClockRight { if input.isCursorClockRight {
return handleInlineCandidateRotation( return handleInlineCandidateRotation(
state: state, reverseModifier: false, stateCallback: stateCallback, errorCallback: errorCallback state: state, reverseModifier: false, stateCallback: stateCallback, errorCallback: errorCallback
@ -285,7 +285,7 @@ extension KeyHandler {
walk() walk()
let inputting = buildInputtingState let inputting = buildInputtingState
stateCallback(inputting) stateCallback(inputting)
stateCallback(buildCandidate(state: inputting, isTypingVertical: input.isTypingVertical)) stateCallback(buildCandidate(state: inputting))
} else { // } else { //
IME.prtDebugIntel("17446655") IME.prtDebugIntel("17446655")
errorCallback() errorCallback()
@ -297,14 +297,14 @@ extension KeyHandler {
// Enter 使 commit buffer // Enter 使 commit buffer
// bool _ = // bool _ =
_ = handleEnter(state: state, stateCallback: stateCallback) _ = handleEnter(state: state, stateCallback: stateCallback)
stateCallback(InputState.SymbolTable(node: SymbolNode.root, isTypingVertical: input.isTypingVertical)) stateCallback(IMEState.SymbolTable(node: SymbolNode.root))
return true return true
} }
} }
// MARK: / (FW / HW Arabic Numbers Input) // MARK: / (FW / HW Arabic Numbers Input)
if state is InputState.Empty { if state.type == .ofEmpty {
if input.isMainAreaNumKey, input.isShiftHold, input.isOptionHold, !input.isControlHold, !input.isCommandHold { if input.isMainAreaNumKey, input.isShiftHold, input.isOptionHold, !input.isControlHold, !input.isCommandHold {
// NOTE: macOS 10.11 El Capitan CFStringTransform StringTransform: // NOTE: macOS 10.11 El Capitan CFStringTransform StringTransform:
// https://developer.apple.com/documentation/foundation/stringtransform // https://developer.apple.com/documentation/foundation/stringtransform
@ -312,9 +312,9 @@ extension KeyHandler {
let string = NSMutableString(string: stringRAW) let string = NSMutableString(string: stringRAW)
CFStringTransform(string, nil, kCFStringTransformFullwidthHalfwidth, true) CFStringTransform(string, nil, kCFStringTransformFullwidthHalfwidth, true)
stateCallback( stateCallback(
InputState.Committing(textToCommit: mgrPrefs.halfWidthPunctuationEnabled ? stringRAW : string as String) IMEState.Committing(textToCommit: mgrPrefs.halfWidthPunctuationEnabled ? stringRAW : string as String)
) )
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
} }
} }
@ -357,10 +357,10 @@ extension KeyHandler {
// MARK: / (Full-Width / Half-Width Space) // MARK: / (Full-Width / Half-Width Space)
/// 使 /// 使
if state is InputState.Empty { if state.type == .ofEmpty {
if input.isSpace, !input.isOptionHold, !input.isControlHold, !input.isCommandHold { if input.isSpace, !input.isOptionHold, !input.isControlHold, !input.isCommandHold {
stateCallback(InputState.Committing(textToCommit: input.isShiftHold ? " " : " ")) stateCallback(IMEState.Committing(textToCommit: input.isShiftHold ? " " : " "))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
} }
} }
@ -371,14 +371,14 @@ extension KeyHandler {
if input.isShiftHold { // isOptionHold if input.isShiftHold { // isOptionHold
switch mgrPrefs.upperCaseLetterKeyBehavior { switch mgrPrefs.upperCaseLetterKeyBehavior {
case 1: case 1:
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
stateCallback(InputState.Committing(textToCommit: inputText.lowercased())) stateCallback(IMEState.Committing(textToCommit: inputText.lowercased()))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
case 2: case 2:
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
stateCallback(InputState.Committing(textToCommit: inputText.uppercased())) stateCallback(IMEState.Committing(textToCommit: inputText.uppercased()))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
default: // case 0 default: // case 0
let letter = "_letter_\(inputText)" let letter = "_letter_\(inputText)"
@ -401,7 +401,7 @@ extension KeyHandler {
/// ///
/// F1-F12 /// F1-F12
/// 便 /// 便
if (state is InputState.NotEmpty) || !composer.isEmpty { if state.hasComposition || !composer.isEmpty {
IME.prtDebugIntel( IME.prtDebugIntel(
"Blocked data: charCode: \(input.charCode), keyCode: \(input.keyCode)") "Blocked data: charCode: \(input.charCode), keyCode: \(input.keyCode)")
IME.prtDebugIntel("A9BFF20E") IME.prtDebugIntel("A9BFF20E")

View File

@ -17,101 +17,73 @@ import Foundation
extension KeyHandler { extension KeyHandler {
// MARK: - State Building // MARK: - State Building
/// ///
var buildInputtingState: InputState.Inputting { var buildInputtingState: IMEState {
/// (Update the composing buffer) /// (Update the composing buffer)
/// NSAttributeString /// NSAttributeString
var tooltipParameterRef: [String] = ["", ""] var displayTextSegments: [String] = compositor.walkedNodes.values.map {
let nodeValuesArray: [String] = compositor.walkedNodes.values.map {
guard let delegate = delegate, delegate.isVerticalTyping else { return $0 } guard let delegate = delegate, delegate.isVerticalTyping else { return $0 }
guard mgrPrefs.hardenVerticalPunctuations else { return $0 } guard mgrPrefs.hardenVerticalPunctuations else { return $0 }
var neta = $0 var neta = $0
ChineseConverter.hardenVerticalPunctuations(target: &neta, convert: delegate.isVerticalTyping) ChineseConverter.hardenVerticalPunctuations(target: &neta, convert: delegate.isVerticalTyping)
return neta return neta
} }
var cursor = convertCursorForDisplay(compositor.cursor)
let reading = composer.getInlineCompositionForDisplay(isHanyuPinyin: mgrPrefs.showHanyuPinyinInCompositionBuffer)
if !reading.isEmpty {
var newDisplayTextSegments = [String]()
var temporaryNode = ""
var charCounter = 0
for node in displayTextSegments {
for char in node {
if charCounter == cursor {
newDisplayTextSegments.append(temporaryNode)
temporaryNode = ""
newDisplayTextSegments.append(reading)
}
temporaryNode += String(char)
charCounter += 1
}
newDisplayTextSegments.append(temporaryNode)
temporaryNode = ""
}
if newDisplayTextSegments == displayTextSegments { newDisplayTextSegments.append(reading) }
displayTextSegments = newDisplayTextSegments
cursor += reading.count
}
/// 使
return IMEState.Inputting(displayTextSegments: displayTextSegments, cursor: cursor)
}
///
func convertCursorForDisplay(_ rawCursor: Int) -> Int {
var composedStringCursorIndex = 0 var composedStringCursorIndex = 0
var readingCursorIndex = 0 var readingCursorIndex = 0
/// IMK UTF8 emoji
/// Swift.utf16NSString.length()
///
for theNode in compositor.walkedNodes { for theNode in compositor.walkedNodes {
let strNodeValue = theNode.value let strNodeValue = theNode.value
let arrSplit: [String] = Array(strNodeValue).charComponents
let codepointCount = arrSplit.count
/// ///
/// NodeAnchorspanningLength /// NodeAnchorspanningLength
/// ///
let spanningLength: Int = theNode.spanLength let spanningLength: Int = theNode.keyArray.count
if readingCursorIndex + spanningLength <= compositor.cursor { if readingCursorIndex + spanningLength <= rawCursor {
composedStringCursorIndex += strNodeValue.utf16.count composedStringCursorIndex += strNodeValue.count
readingCursorIndex += spanningLength readingCursorIndex += spanningLength
continue continue
} }
if codepointCount == spanningLength { if !theNode.isReadingMismatched {
for i in 0..<codepointCount { for _ in 0..<strNodeValue.count {
guard readingCursorIndex < compositor.cursor else { continue } guard readingCursorIndex < rawCursor else { continue }
composedStringCursorIndex += arrSplit[i].utf16.count composedStringCursorIndex += 1
readingCursorIndex += 1 readingCursorIndex += 1
} }
continue continue
} }
guard readingCursorIndex < compositor.cursor else { continue } guard readingCursorIndex < rawCursor else { continue }
composedStringCursorIndex += strNodeValue.utf16.count composedStringCursorIndex += strNodeValue.count
readingCursorIndex += spanningLength readingCursorIndex += spanningLength
readingCursorIndex = min(readingCursorIndex, compositor.cursor) readingCursorIndex = min(readingCursorIndex, rawCursor)
///
///
///
///
///
switch compositor.cursor {
case compositor.keys.count...:
// compositor.cursor readings.count Megrez
tooltipParameterRef[0] = compositor.keys[compositor.cursor - 1]
case 0:
tooltipParameterRef[1] = compositor.keys[compositor.cursor]
default:
tooltipParameterRef[0] = compositor.keys[compositor.cursor - 1]
tooltipParameterRef[1] = compositor.keys[compositor.cursor]
}
} }
return composedStringCursorIndex
isCursorCuttingChar = !tooltipParameterRef[0].isEmpty || !tooltipParameterRef[1].isEmpty
///
/// 便 composer
var arrHead = [String.UTF16View.Element]()
var arrTail = [String.UTF16View.Element]()
for (i, n) in nodeValuesArray.joined().utf16.enumerated() {
if i < composedStringCursorIndex {
arrHead.append(n)
} else {
arrTail.append(n)
}
}
/// stringview
///
let head = String(utf16CodeUnits: arrHead, count: arrHead.count)
let reading = composer.getInlineCompositionForDisplay(isHanyuPinyin: mgrPrefs.showHanyuPinyinInCompositionBuffer)
let tail = String(utf16CodeUnits: arrTail, count: arrTail.count)
let composedText = head + reading + tail
let cursorIndex = composedStringCursorIndex + reading.utf16.count
//
var cleanedComposition = ""
for theChar in composedText {
guard let charCode = theChar.utf16.first else { continue }
if !(theChar.isASCII && !(charCode.isPrintable)) {
cleanedComposition += String(theChar)
}
}
/// 使
return InputState.Inputting(
displayedText: cleanedComposition, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
)
} }
// MARK: - // MARK: -
@ -122,14 +94,13 @@ extension KeyHandler {
/// - isTypingVertical: /// - isTypingVertical:
/// - Returns: /// - Returns:
func buildCandidate( func buildCandidate(
state currentState: InputState.NotEmpty, state currentState: IMEStateProtocol,
isTypingVertical _: Bool = false isTypingVertical _: Bool = false
) -> InputState.ChoosingCandidate { ) -> IMEState {
InputState.ChoosingCandidate( IMEState.Candidates(
displayedText: currentState.displayedText,
cursorIndex: currentState.cursorIndex,
candidates: getCandidatesArray(fixOrder: mgrPrefs.useFixecCandidateOrderOnSelection), candidates: getCandidatesArray(fixOrder: mgrPrefs.useFixecCandidateOrderOnSelection),
nodeValuesArray: compositor.walkedNodes.values displayTextSegments: compositor.walkedNodes.values,
cursor: currentState.data.cursor
) )
} }
@ -149,9 +120,9 @@ extension KeyHandler {
/// - Returns: /// - Returns:
func buildAssociatePhraseState( func buildAssociatePhraseState(
withPair pair: Megrez.Compositor.KeyValuePaired withPair pair: Megrez.Compositor.KeyValuePaired
) -> InputState.Associates! { ) -> IMEState {
//  Xcode //  Xcode
InputState.Associates( IMEState.Associates(
candidates: buildAssociatePhraseArray(withPair: pair)) candidates: buildAssociatePhraseArray(withPair: pair))
} }
@ -165,9 +136,9 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleMarkingState( func handleMarkingState(
_ state: InputState.Marking, _ state: IMEStateProtocol,
input: InputSignalProtocol, input: InputSignalProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
if input.isEsc { if input.isEsc {
@ -220,18 +191,19 @@ extension KeyHandler {
// Shift + Left // Shift + Left
if input.isCursorBackward || input.emacsKey == EmacsKey.backward, input.isShiftHold { if input.isCursorBackward || input.emacsKey == EmacsKey.backward, input.isShiftHold {
var index = state.markerIndex if compositor.marker > 0 {
if index > 0 { compositor.marker -= 1
index = state.displayedText.utf16PreviousPosition(for: index) if isCursorCuttingChar(isMarker: true) {
let marking = InputState.Marking( compositor.jumpCursorBySpan(to: .rear, isMarker: true)
displayedText: state.displayedText, }
cursorIndex: state.cursorIndex, var marking = IMEState.Marking(
markerIndex: index, displayTextSegments: state.data.displayTextSegments,
readings: state.readings, markedReadings: Array(compositor.keys[currentMarkedRange()]),
nodeValuesArray: compositor.walkedNodes.values cursor: convertCursorForDisplay(compositor.cursor),
marker: convertCursorForDisplay(compositor.marker)
) )
marking.tooltipBackupForInputting = state.tooltipBackupForInputting marking.data.tooltipBackupForInputting = state.data.tooltipBackupForInputting
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking) stateCallback(marking.data.markedRange.isEmpty ? marking.convertedToInputting : marking)
} else { } else {
IME.prtDebugIntel("1149908D") IME.prtDebugIntel("1149908D")
errorCallback() errorCallback()
@ -242,18 +214,19 @@ extension KeyHandler {
// Shift + Right // Shift + Right
if input.isCursorForward || input.emacsKey == EmacsKey.forward, input.isShiftHold { if input.isCursorForward || input.emacsKey == EmacsKey.forward, input.isShiftHold {
var index = state.markerIndex if compositor.marker < compositor.width {
if index < (state.displayedText.utf16.count) { compositor.marker += 1
index = state.displayedText.utf16NextPosition(for: index) if isCursorCuttingChar(isMarker: true) {
let marking = InputState.Marking( compositor.jumpCursorBySpan(to: .front, isMarker: true)
displayedText: state.displayedText, }
cursorIndex: state.cursorIndex, var marking = IMEState.Marking(
markerIndex: index, displayTextSegments: state.data.displayTextSegments,
readings: state.readings, markedReadings: Array(compositor.keys[currentMarkedRange()]),
nodeValuesArray: compositor.walkedNodes.values cursor: convertCursorForDisplay(compositor.cursor),
marker: convertCursorForDisplay(compositor.marker)
) )
marking.tooltipBackupForInputting = state.tooltipBackupForInputting marking.data.tooltipBackupForInputting = state.data.tooltipBackupForInputting
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking) stateCallback(marking.data.markedRange.isEmpty ? marking.convertedToInputting : marking)
} else { } else {
IME.prtDebugIntel("9B51408D") IME.prtDebugIntel("9B51408D")
errorCallback() errorCallback()
@ -276,9 +249,9 @@ extension KeyHandler {
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handlePunctuation( func handlePunctuation(
_ customPunctuation: String, _ customPunctuation: String,
state: InputStateProtocol, state: IMEStateProtocol,
usingVerticalTyping isTypingVertical: Bool, usingVerticalTyping isTypingVertical: Bool,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
if !currentLM.hasUnigramsFor(key: customPunctuation) { if !currentLM.hasUnigramsFor(key: customPunctuation) {
@ -308,8 +281,8 @@ extension KeyHandler {
if candidateState.candidates.count == 1 { if candidateState.candidates.count == 1 {
clear() // candidateState clear() // candidateState
if let candidateToCommit: (String, String) = candidateState.candidates.first, !candidateToCommit.1.isEmpty { if let candidateToCommit: (String, String) = candidateState.candidates.first, !candidateToCommit.1.isEmpty {
stateCallback(InputState.Committing(textToCommit: candidateToCommit.1)) stateCallback(IMEState.Committing(textToCommit: candidateToCommit.1))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
} else { } else {
stateCallback(candidateState) stateCallback(candidateState)
} }
@ -327,13 +300,13 @@ extension KeyHandler {
/// - stateCallback: /// - stateCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleEnter( func handleEnter(
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void stateCallback: @escaping (IMEStateProtocol) -> Void
) -> Bool { ) -> Bool {
guard let currentState = state as? InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
stateCallback(InputState.Committing(textToCommit: currentState.displayedText)) stateCallback(IMEState.Committing(textToCommit: state.displayedText))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
} }
@ -345,10 +318,10 @@ extension KeyHandler {
/// - stateCallback: /// - stateCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleCtrlCommandEnter( func handleCtrlCommandEnter(
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void stateCallback: @escaping (IMEStateProtocol) -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
var displayedText = compositor.keys.joined(separator: "-") var displayedText = compositor.keys.joined(separator: "-")
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin { if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
@ -360,8 +333,8 @@ extension KeyHandler {
displayedText = displayedText.replacingOccurrences(of: "-", with: " ") displayedText = displayedText.replacingOccurrences(of: "-", with: " ")
} }
stateCallback(InputState.Committing(textToCommit: displayedText)) stateCallback(IMEState.Committing(textToCommit: displayedText))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
} }
@ -373,10 +346,10 @@ extension KeyHandler {
/// - stateCallback: /// - stateCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleCtrlOptionCommandEnter( func handleCtrlOptionCommandEnter(
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void stateCallback: @escaping (IMEStateProtocol) -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
var composed = "" var composed = ""
@ -396,8 +369,8 @@ extension KeyHandler {
composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>" composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>"
} }
stateCallback(InputState.Committing(textToCommit: composed)) stateCallback(IMEState.Committing(textToCommit: composed))
stateCallback(InputState.Empty()) stateCallback(IMEState.Empty())
return true return true
} }
@ -411,12 +384,12 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleBackSpace( func handleBackSpace(
state: InputStateProtocol, state: IMEStateProtocol,
input: InputSignalProtocol, input: InputSignalProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
// macOS Shift+BackSpace // macOS Shift+BackSpace
switch mgrPrefs.specifyShiftBackSpaceKeyBehavior { switch mgrPrefs.specifyShiftBackSpaceKeyBehavior {
@ -430,15 +403,13 @@ extension KeyHandler {
stateCallback(buildInputtingState) stateCallback(buildInputtingState)
return true return true
case 1: case 1:
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
return true return true
default: break default: break
} }
if input.isShiftHold, input.isOptionHold { if input.isShiftHold, input.isOptionHold {
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
return true return true
} }
@ -461,8 +432,7 @@ extension KeyHandler {
switch composer.isEmpty && compositor.isEmpty { switch composer.isEmpty && compositor.isEmpty {
case false: stateCallback(buildInputtingState) case false: stateCallback(buildInputtingState)
case true: case true:
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
} }
return true return true
} }
@ -477,16 +447,15 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleDelete( func handleDelete(
state: InputStateProtocol, state: IMEStateProtocol,
input: InputSignalProtocol, input: InputSignalProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
if input.isShiftHold { if input.isShiftHold {
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
return true return true
} }
@ -509,8 +478,7 @@ extension KeyHandler {
switch inputting.displayedText.isEmpty { switch inputting.displayedText.isEmpty {
case false: stateCallback(inputting) case false: stateCallback(inputting)
case true: case true:
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
} }
return true return true
} }
@ -524,11 +492,11 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleClockKey( func handleClockKey(
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
if !composer.isEmpty { if !composer.isEmpty {
IME.prtDebugIntel("9B6F908D") IME.prtDebugIntel("9B6F908D")
errorCallback() errorCallback()
@ -546,11 +514,11 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleHome( func handleHome(
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
if !composer.isEmpty { if !composer.isEmpty {
IME.prtDebugIntel("ABC44080") IME.prtDebugIntel("ABC44080")
@ -580,11 +548,11 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleEnd( func handleEnd(
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
if !composer.isEmpty { if !composer.isEmpty {
IME.prtDebugIntel("9B69908D") IME.prtDebugIntel("9B69908D")
@ -613,16 +581,15 @@ extension KeyHandler {
/// - stateCallback: /// - stateCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleEsc( func handleEsc(
state: InputStateProtocol, state: IMEStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void stateCallback: @escaping (IMEStateProtocol) -> Void
) -> Bool { ) -> Bool {
guard state is InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
if mgrPrefs.escToCleanInputBuffer { if mgrPrefs.escToCleanInputBuffer {
/// ///
/// macOS Windows 使 /// macOS Windows 使
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
} else { } else {
if composer.isEmpty { return true } if composer.isEmpty { return true }
/// ///
@ -630,8 +597,7 @@ extension KeyHandler {
switch compositor.isEmpty { switch compositor.isEmpty {
case false: stateCallback(buildInputtingState) case false: stateCallback(buildInputtingState)
case true: case true:
stateCallback(InputState.Abortion()) stateCallback(IMEState.Abortion())
stateCallback(InputState.Empty())
} }
} }
return true return true
@ -647,12 +613,12 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleForward( func handleForward(
state: InputStateProtocol, state: IMEStateProtocol,
input: InputSignalProtocol, input: InputSignalProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard let currentState = state as? InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
if !composer.isEmpty { if !composer.isEmpty {
IME.prtDebugIntel("B3BA5257") IME.prtDebugIntel("B3BA5257")
@ -663,16 +629,18 @@ extension KeyHandler {
if input.isShiftHold { if input.isShiftHold {
// Shift + Right // Shift + Right
if currentState.cursorIndex < currentState.displayedText.utf16.count { if compositor.cursor < compositor.width {
let nextPosition = currentState.displayedText.utf16NextPosition( compositor.marker = compositor.cursor + 1
for: currentState.cursorIndex) if isCursorCuttingChar(isMarker: true) {
let marking: InputState.Marking! = InputState.Marking( compositor.jumpCursorBySpan(to: .front, isMarker: true)
displayedText: currentState.displayedText, }
cursorIndex: currentState.cursorIndex, var marking = IMEState.Marking(
markerIndex: nextPosition, displayTextSegments: compositor.walkedNodes.values,
readings: compositor.keys markedReadings: Array(compositor.keys[currentMarkedRange()]),
cursor: convertCursorForDisplay(compositor.cursor),
marker: convertCursorForDisplay(compositor.marker)
) )
marking.tooltipBackupForInputting = currentState.tooltip marking.data.tooltipBackupForInputting = state.tooltip
stateCallback(marking) stateCallback(marking)
} else { } else {
IME.prtDebugIntel("BB7F6DB9") IME.prtDebugIntel("BB7F6DB9")
@ -680,7 +648,6 @@ extension KeyHandler {
stateCallback(state) stateCallback(state)
} }
} else if input.isOptionHold { } else if input.isOptionHold {
isCursorCuttingChar = false
if input.isControlHold { if input.isControlHold {
return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback) return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
} }
@ -695,12 +662,10 @@ extension KeyHandler {
} else { } else {
if compositor.cursor < compositor.length { if compositor.cursor < compositor.length {
compositor.cursor += 1 compositor.cursor += 1
var inputtingState = buildInputtingState if isCursorCuttingChar() {
if isCursorCuttingChar == true {
compositor.jumpCursorBySpan(to: .front) compositor.jumpCursorBySpan(to: .front)
inputtingState = buildInputtingState
} }
stateCallback(inputtingState) stateCallback(buildInputtingState)
} else { } else {
IME.prtDebugIntel("A96AAD58") IME.prtDebugIntel("A96AAD58")
errorCallback() errorCallback()
@ -721,12 +686,12 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleBackward( func handleBackward(
state: InputStateProtocol, state: IMEStateProtocol,
input: InputSignalProtocol, input: InputSignalProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
guard let currentState = state as? InputState.Inputting else { return false } guard state.type == .ofInputting else { return false }
if !composer.isEmpty { if !composer.isEmpty {
IME.prtDebugIntel("6ED95318") IME.prtDebugIntel("6ED95318")
@ -737,16 +702,18 @@ extension KeyHandler {
if input.isShiftHold { if input.isShiftHold {
// Shift + left // Shift + left
if currentState.cursorIndex > 0 { if compositor.cursor > 0 {
let previousPosition = currentState.displayedText.utf16PreviousPosition( compositor.marker = compositor.cursor - 1
for: currentState.cursorIndex) if isCursorCuttingChar(isMarker: true) {
let marking: InputState.Marking! = InputState.Marking( compositor.jumpCursorBySpan(to: .rear, isMarker: true)
displayedText: currentState.displayedText, }
cursorIndex: currentState.cursorIndex, var marking = IMEState.Marking(
markerIndex: previousPosition, displayTextSegments: compositor.walkedNodes.values,
readings: compositor.keys markedReadings: Array(compositor.keys[currentMarkedRange()]),
cursor: convertCursorForDisplay(compositor.cursor),
marker: convertCursorForDisplay(compositor.marker)
) )
marking.tooltipBackupForInputting = currentState.tooltip marking.data.tooltipBackupForInputting = state.tooltip
stateCallback(marking) stateCallback(marking)
} else { } else {
IME.prtDebugIntel("D326DEA3") IME.prtDebugIntel("D326DEA3")
@ -754,7 +721,6 @@ extension KeyHandler {
stateCallback(state) stateCallback(state)
} }
} else if input.isOptionHold { } else if input.isOptionHold {
isCursorCuttingChar = false
if input.isControlHold { if input.isControlHold {
return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback) return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
} }
@ -769,12 +735,10 @@ extension KeyHandler {
} else { } else {
if compositor.cursor > 0 { if compositor.cursor > 0 {
compositor.cursor -= 1 compositor.cursor -= 1
var inputtingState = buildInputtingState if isCursorCuttingChar() {
if isCursorCuttingChar == true {
compositor.jumpCursorBySpan(to: .rear) compositor.jumpCursorBySpan(to: .rear)
inputtingState = buildInputtingState
} }
stateCallback(inputtingState) stateCallback(buildInputtingState)
} else { } else {
IME.prtDebugIntel("7045E6F3") IME.prtDebugIntel("7045E6F3")
errorCallback() errorCallback()
@ -795,14 +759,14 @@ extension KeyHandler {
/// - errorCallback: /// - errorCallback:
/// - Returns: ctlInputMethod IMK /// - Returns: ctlInputMethod IMK
func handleInlineCandidateRotation( func handleInlineCandidateRotation(
state: InputStateProtocol, state: IMEStateProtocol,
reverseModifier: Bool, reverseModifier: Bool,
stateCallback: @escaping (InputStateProtocol) -> Void, stateCallback: @escaping (IMEStateProtocol) -> Void,
errorCallback: @escaping () -> Void errorCallback: @escaping () -> Void
) -> Bool { ) -> Bool {
if composer.isEmpty, compositor.isEmpty || compositor.walkedNodes.isEmpty { return false } if composer.isEmpty, compositor.isEmpty || compositor.walkedNodes.isEmpty { return false }
guard state is InputState.Inputting else { guard state.type == .ofInputting else {
guard state is InputState.Empty else { guard state.type == .ofEmpty else {
IME.prtDebugIntel("6044F081") IME.prtDebugIntel("6044F081")
errorCallback() errorCallback()
return true return true

View File

@ -31,9 +31,9 @@ extension ctlInputMethod {
if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) { if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) {
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n" message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n"
+ toggleASCIIMode() + (toggleASCIIMode()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
if shouldUseHandle { if shouldUseHandle {

View File

@ -32,16 +32,20 @@ class ctlInputMethod: IMKInputController {
// MARK: - // MARK: -
/// 調
var keyHandler: KeyHandler = .init()
///
var state: InputStateProtocol = InputState.Empty()
/// ctlInputMethod
var isASCIIMode: Bool = false
/// ctlInputMethod /// ctlInputMethod
static var isASCIIModeSituation: Bool = false static var isASCIIModeSituation: Bool = false
/// ctlInputMethod /// ctlInputMethod
static var isVerticalTypingSituation: Bool = false static var isVerticalTypingSituation: Bool = false
/// ctlInputMethod
var isASCIIMode: Bool = false
/// 調
var keyHandler: KeyHandler = .init()
///
var state: IMEStateProtocol = IMEState.Empty() {
didSet {
IME.prtDebugIntel("Current State: \(state.type.rawValue)")
}
}
/// ctlInputMethod /// ctlInputMethod
func toggleASCIIMode() -> Bool { func toggleASCIIMode() -> Bool {
@ -65,15 +69,15 @@ class ctlInputMethod: IMKInputController {
/// 調 /// 調
func resetKeyHandler() { func resetKeyHandler() {
// //
if state is InputState.Inputting, mgrPrefs.trimUnfinishedReadingsOnCommit { if state.type == .ofInputting, mgrPrefs.trimUnfinishedReadingsOnCommit {
keyHandler.composer.clear() keyHandler.composer.clear()
handle(state: keyHandler.buildInputtingState) handle(state: keyHandler.buildInputtingState)
} }
if let state = state as? InputState.NotEmpty { if state.hasComposition {
/// 調 /// 調
handle(state: InputState.Committing(textToCommit: state.displayedTextConverted)) handle(state: IMEState.Committing(textToCommit: state.displayedText))
} }
handle(state: InputState.Empty()) handle(state: IMEState.Empty())
} }
// MARK: - IMKInputController // MARK: - IMKInputController
@ -115,9 +119,9 @@ class ctlInputMethod: IMKInputController {
} else { } else {
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n" message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n"
+ isASCIIMode + (isASCIIMode
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
} }
@ -127,7 +131,7 @@ class ctlInputMethod: IMKInputController {
if let client = client(), client.bundleIdentifier() != Bundle.main.bundleIdentifier { if let client = client(), client.bundleIdentifier() != Bundle.main.bundleIdentifier {
// 使 // 使
setKeyLayout() setKeyLayout()
handle(state: InputState.Empty()) handle(state: IMEState.Empty())
} // } //
(NSApp.delegate as? AppDelegate)?.checkForUpdate() (NSApp.delegate as? AppDelegate)?.checkForUpdate()
} }
@ -137,7 +141,7 @@ class ctlInputMethod: IMKInputController {
override func deactivateServer(_ sender: Any!) { override func deactivateServer(_ sender: Any!) {
_ = sender // _ = sender //
resetKeyHandler() // Empty resetKeyHandler() // Empty
handle(state: InputState.Deactivated()) handle(state: IMEState.Deactivated())
} }
/// ///
@ -168,7 +172,7 @@ class ctlInputMethod: IMKInputController {
if let client = client(), client.bundleIdentifier() != Bundle.main.bundleIdentifier { if let client = client(), client.bundleIdentifier() != Bundle.main.bundleIdentifier {
// 使 // 使
setKeyLayout() setKeyLayout()
handle(state: InputState.Empty()) handle(state: IMEState.Empty())
} // } //
} }
@ -286,8 +290,8 @@ class ctlInputMethod: IMKInputController {
/// - Returns: nil /// - Returns: nil
override func composedString(_ sender: Any!) -> Any! { override func composedString(_ sender: Any!) -> Any! {
_ = sender // _ = sender //
guard let state = state as? InputState.NotEmpty else { return "" } guard state.hasComposition else { return "" }
return state.committingBufferConverted return state.displayedText
} }
/// ///
@ -307,7 +311,7 @@ class ctlInputMethod: IMKInputController {
_ = sender // _ = sender //
var arrResult = [String]() var arrResult = [String]()
// 便 InputState // 便 IMEState
func handleCandidatesPrepared(_ candidates: [(String, String)], prefix: String = "") { func handleCandidatesPrepared(_ candidates: [(String, String)], prefix: String = "") {
for theCandidate in candidates { for theCandidate in candidates {
let theConverted = IME.kanjiConversionIfRequired(theCandidate.1) let theConverted = IME.kanjiConversionIfRequired(theCandidate.1)
@ -323,12 +327,12 @@ class ctlInputMethod: IMKInputController {
} }
} }
if let state = state as? InputState.Associates { if state.type == .ofAssociates {
handleCandidatesPrepared(state.candidates, prefix: "") handleCandidatesPrepared(state.candidates, prefix: "")
} else if let state = state as? InputState.SymbolTable { } else if state.type == .ofSymbolTable {
// / JIS 使 // / JIS 使
arrResult = state.candidates.map(\.1) arrResult = state.candidates.map(\.1)
} else if let state = state as? InputState.ChoosingCandidate { } else if state.type == .ofCandidates {
guard !state.candidates.isEmpty else { return .init() } guard !state.candidates.isEmpty else { return .init() }
if state.candidates[0].0.contains("_punctuation") { if state.candidates[0].0.contains("_punctuation") {
arrResult = state.candidates.map(\.1) // arrResult = state.candidates.map(\.1) //
@ -361,17 +365,16 @@ class ctlInputMethod: IMKInputController {
/// - Parameter candidateString: /// - Parameter candidateString:
override open func candidateSelected(_ candidateString: NSAttributedString!) { override open func candidateSelected(_ candidateString: NSAttributedString!) {
let candidateString: NSAttributedString = candidateString ?? .init(string: "") let candidateString: NSAttributedString = candidateString ?? .init(string: "")
if state is InputState.Associates { if state.type == .ofAssociates {
if !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter { if !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter {
handle(state: InputState.Abortion()) handle(state: IMEState.Abortion())
handle(state: InputState.Empty())
return return
} }
} }
var indexDeducted = 0 var indexDeducted = 0
// 便 InputState // 便 IMEState
func handleCandidatesSelected(_ candidates: [(String, String)], prefix: String = "") { func handleCandidatesSelected(_ candidates: [(String, String)], prefix: String = "") {
for (i, neta) in candidates.enumerated() { for (i, neta) in candidates.enumerated() {
let theConverted = IME.kanjiConversionIfRequired(neta.1) let theConverted = IME.kanjiConversionIfRequired(neta.1)
@ -401,11 +404,11 @@ class ctlInputMethod: IMKInputController {
} }
} }
if let state = state as? InputState.Associates { if state.type == .ofAssociates {
handleCandidatesSelected(state.candidates, prefix: "") handleCandidatesSelected(state.candidates, prefix: "")
} else if let state = state as? InputState.SymbolTable { } else if state.type == .ofSymbolTable {
handleSymbolCandidatesSelected(state.candidates) handleSymbolCandidatesSelected(state.candidates)
} else if let state = state as? InputState.ChoosingCandidate { } else if state.type == .ofCandidates {
guard !state.candidates.isEmpty else { return } guard !state.candidates.isEmpty else { return }
if state.candidates[0].0.contains("_punctuation") { if state.candidates[0].0.contains("_punctuation") {
handleSymbolCandidatesSelected(state.candidates) // handleSymbolCandidatesSelected(state.candidates) //

View File

@ -37,21 +37,20 @@ extension ctlInputMethod: KeyHandlerDelegate {
ctlCandidate(controller, didSelectCandidateAtIndex: index) ctlCandidate(controller, didSelectCandidateAtIndex: index)
} }
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol, addToFilter: Bool) func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: IMEStateProtocol, addToFilter: Bool)
-> Bool -> Bool
{ {
guard let state = state as? InputState.Marking else { return false } guard state.type == .ofMarking else { return false }
if state.bufferReadingCountMisMatch { return false }
let refInputModeReversed: InputMode = let refInputModeReversed: InputMode =
(keyHandler.inputMode == InputMode.imeModeCHT) (keyHandler.inputMode == InputMode.imeModeCHT)
? InputMode.imeModeCHS : InputMode.imeModeCHT ? InputMode.imeModeCHS : InputMode.imeModeCHT
if !mgrLangModel.writeUserPhrase( if !mgrLangModel.writeUserPhrase(
state.userPhrase, inputMode: keyHandler.inputMode, state.data.userPhrase, inputMode: keyHandler.inputMode,
areWeDuplicating: state.chkIfUserPhraseExists, areWeDuplicating: state.data.chkIfUserPhraseExists,
areWeDeleting: addToFilter areWeDeleting: addToFilter
) )
|| !mgrLangModel.writeUserPhrase( || !mgrLangModel.writeUserPhrase(
state.userPhraseConverted, inputMode: refInputModeReversed, state.data.userPhraseConverted, inputMode: refInputModeReversed,
areWeDuplicating: false, areWeDuplicating: false,
areWeDeleting: addToFilter areWeDeleting: addToFilter
) )
@ -65,7 +64,7 @@ extension ctlInputMethod: KeyHandlerDelegate {
// MARK: - Candidate Controller Delegate // MARK: - Candidate Controller Delegate
extension ctlInputMethod: ctlCandidateDelegate { extension ctlInputMethod: ctlCandidateDelegate {
var isAssociatedPhrasesState: Bool { state is InputState.Associates } var isAssociatedPhrasesState: Bool { state.type == .ofAssociates }
/// handle() IMK /// handle() IMK
/// handle() /// handle()
@ -78,9 +77,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int { func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int {
_ = controller // _ = controller //
if let state = state as? InputState.ChoosingCandidate { if state.isCandidateContainer {
return state.candidates.count
} else if let state = state as? InputState.Associates {
return state.candidates.count return state.candidates.count
} }
return 0 return 0
@ -91,9 +88,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
/// - Returns: /// - Returns:
func candidatesForController(_ controller: ctlCandidateProtocol) -> [(String, String)] { func candidatesForController(_ controller: ctlCandidateProtocol) -> [(String, String)] {
_ = controller // _ = controller //
if let state = state as? InputState.ChoosingCandidate { if state.isCandidateContainer {
return state.candidates
} else if let state = state as? InputState.Associates {
return state.candidates return state.candidates
} }
return .init() return .init()
@ -103,9 +98,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
-> (String, String) -> (String, String)
{ {
_ = controller // _ = controller //
if let state = state as? InputState.ChoosingCandidate { if state.isCandidateContainer {
return state.candidates[index]
} else if let state = state as? InputState.Associates {
return state.candidates[index] return state.candidates[index]
} }
return ("", "") return ("", "")
@ -114,20 +107,20 @@ extension ctlInputMethod: ctlCandidateDelegate {
func ctlCandidate(_ controller: ctlCandidateProtocol, didSelectCandidateAtIndex index: Int) { func ctlCandidate(_ controller: ctlCandidateProtocol, didSelectCandidateAtIndex index: Int) {
_ = controller // _ = controller //
if let state = state as? InputState.SymbolTable, if state.type == .ofSymbolTable,
let node = state.node.children?[index] let node = state.node.children?[index]
{ {
if let children = node.children, !children.isEmpty { if let children = node.children, !children.isEmpty {
handle(state: InputState.Empty()) // handle(state: IMEState.Empty()) //
handle(state: InputState.SymbolTable(node: node, previous: state.node)) handle(state: IMEState.SymbolTable(node: node))
} else { } else {
handle(state: InputState.Committing(textToCommit: node.title)) handle(state: IMEState.Committing(textToCommit: node.title))
handle(state: InputState.Empty()) handle(state: IMEState.Empty())
} }
return return
} }
if let state = state as? InputState.ChoosingCandidate { if [.ofCandidates, .ofSymbolTable].contains(state.type) {
let selectedValue = state.candidates[index] let selectedValue = state.candidates[index]
keyHandler.fixNode( keyHandler.fixNode(
candidate: selectedValue, respectCursorPushing: true, candidate: selectedValue, respectCursorPushing: true,
@ -137,16 +130,15 @@ extension ctlInputMethod: ctlCandidateDelegate {
let inputting = keyHandler.buildInputtingState let inputting = keyHandler.buildInputtingState
if mgrPrefs.useSCPCTypingMode { if mgrPrefs.useSCPCTypingMode {
handle(state: InputState.Committing(textToCommit: inputting.displayedTextConverted)) handle(state: IMEState.Committing(textToCommit: inputting.displayedText))
// selectedValue.1 // selectedValue.1
if mgrPrefs.associatedPhrasesEnabled, if mgrPrefs.associatedPhrasesEnabled {
let associatePhrases = keyHandler.buildAssociatePhraseState( let associates = keyHandler.buildAssociatePhraseState(
withPair: .init(key: selectedValue.0, value: selectedValue.1) withPair: .init(key: selectedValue.0, value: selectedValue.1)
), !associatePhrases.candidates.isEmpty )
{ handle(state: associates.candidates.isEmpty ? IMEState.Empty() : associates)
handle(state: associatePhrases)
} else { } else {
handle(state: InputState.Empty()) handle(state: IMEState.Empty())
} }
} else { } else {
handle(state: inputting) handle(state: inputting)
@ -154,24 +146,25 @@ extension ctlInputMethod: ctlCandidateDelegate {
return return
} }
if let state = state as? InputState.Associates { if state.type == .ofAssociates {
let selectedValue = state.candidates[index] let selectedValue = state.candidates[index]
handle(state: InputState.Committing(textToCommit: selectedValue.1)) handle(state: IMEState.Committing(textToCommit: selectedValue.1))
// selectedValue.1 // selectedValue.1
// //
guard let valueKept = selectedValue.1.last else { guard let valueKept = selectedValue.1.last else {
handle(state: InputState.Empty()) handle(state: IMEState.Empty())
return return
} }
if mgrPrefs.associatedPhrasesEnabled, if mgrPrefs.associatedPhrasesEnabled {
let associatePhrases = keyHandler.buildAssociatePhraseState( let associates = keyHandler.buildAssociatePhraseState(
withPair: .init(key: selectedValue.0, value: String(valueKept)) withPair: .init(key: selectedValue.0, value: String(valueKept))
), !associatePhrases.candidates.isEmpty )
{ if !associates.candidates.isEmpty {
handle(state: associatePhrases) handle(state: associates)
return return
}
} }
handle(state: InputState.Empty()) handle(state: IMEState.Empty())
} }
} }
} }

View File

@ -13,10 +13,10 @@ import Cocoa
// MARK: - Tooltip Display and Candidate Display Methods // MARK: - Tooltip Display and Candidate Display Methods
extension ctlInputMethod { extension ctlInputMethod {
func show(tooltip: String, displayedText: String, cursorIndex: Int) { func show(tooltip: String, displayedText: String, u16Cursor: Int) {
guard let client = client() else { return } guard let client = client() else { return }
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
var cursor = cursorIndex var cursor = u16Cursor
if cursor == displayedText.count, cursor != 0 { if cursor == displayedText.count, cursor != 0 {
cursor -= 1 cursor -= 1
} }
@ -38,24 +38,14 @@ extension ctlInputMethod {
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: finalOrigin) ctlInputMethod.tooltipController.show(tooltip: tooltip, at: finalOrigin)
} }
func show(candidateWindowWith state: InputStateProtocol) { func show(candidateWindowWith state: IMEStateProtocol) {
guard let client = client() else { return } guard let client = client() else { return }
var isTypingVertical: Bool {
if state.type == .ofCandidates {
return ctlInputMethod.isVerticalTypingSituation
} else if state.type == ..ofAssociates {
return ctlInputMethod.isVerticalTypingSituation
}
return false
}
var isCandidateWindowVertical: Bool { var isCandidateWindowVertical: Bool {
var candidates: [(String, String)] = .init() var candidates: [(String, String)] = .init()
if let state = state as? InputState.ChoosingCandidate { if state.isCandidateContainer {
candidates = state.candidates
} else if let state = state as? InputState.Associates {
candidates = state.candidates candidates = state.candidates
} }
if isTypingVertical { return true } if isVerticalTyping { return true }
// IMK // IMK
guard ctlInputMethod.ctlCandidateCurrent is ctlCandidateUniversal else { return false } guard ctlInputMethod.ctlCandidateCurrent is ctlCandidateUniversal else { return false }
// 使 // 使
@ -106,7 +96,7 @@ extension ctlInputMethod {
let candidateKeys = mgrPrefs.candidateKeys let candidateKeys = mgrPrefs.candidateKeys
let keyLabels = let keyLabels =
candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys)
let keyLabelSuffix = state is InputState.Associates ? "^" : "" let keyLabelSuffix = state.type == .ofAssociates ? "^" : ""
ctlInputMethod.ctlCandidateCurrent.keyLabels = keyLabels.map { ctlInputMethod.ctlCandidateCurrent.keyLabels = keyLabels.map {
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
} }
@ -126,8 +116,8 @@ extension ctlInputMethod {
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
var cursor = 0 var cursor = 0
if let state = state as? InputState.ChoosingCandidate { if [.ofCandidates, .ofSymbolTable].contains(state.type) {
cursor = state.cursorIndex cursor = state.data.cursor
if cursor == state.displayedText.count, cursor != 0 { if cursor == state.displayedText.count, cursor != 0 {
cursor -= 1 cursor -= 1
} }
@ -140,7 +130,7 @@ extension ctlInputMethod {
cursor -= 1 cursor -= 1
} }
if isTypingVertical { if isVerticalTyping {
ctlInputMethod.ctlCandidateCurrent.set( ctlInputMethod.ctlCandidateCurrent.set(
windowTopLeftPoint: NSPoint( windowTopLeftPoint: NSPoint(
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0 x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0

View File

@ -18,29 +18,77 @@ extension ctlInputMethod {
/// ///
/// ///
/// - Parameter newState: /// - Parameter newState:
func handle(state newState: InputStateProtocol) { func handle(state newState: IMEStateProtocol) {
let prevState = state let previous = state
state = newState state = newState
switch state.type {
switch newState { case .ofDeactivated:
case let newState as InputState.Deactivated: ctlInputMethod.ctlCandidateCurrent.delegate = nil
handle(state: newState, previous: prevState) ctlInputMethod.ctlCandidateCurrent.visible = false
case let newState as InputState.Empty: ctlInputMethod.tooltipController.hide()
handle(state: newState, previous: prevState) if previous.hasComposition {
case let newState as InputState.Abortion: commit(text: previous.displayedText)
handle(state: newState, previous: prevState) }
case let newState as InputState.Committing: clearInlineDisplay()
handle(state: newState, previous: prevState) //
case let newState as InputState.Inputting: keyHandler.clear()
handle(state: newState, previous: prevState) case .ofEmpty, .ofAbortion:
case let newState as InputState.Marking: var previous = previous
handle(state: newState, previous: prevState) if state.type == .ofAbortion {
case let newState as InputState.ChoosingCandidate: state = IMEState.Empty()
handle(state: newState, previous: prevState) previous = state
case let newState as InputState.Associates: }
handle(state: newState, previous: prevState) ctlInputMethod.ctlCandidateCurrent.visible = false
case let newState as InputState.SymbolTable: ctlInputMethod.tooltipController.hide()
handle(state: newState, previous: prevState) // .Abortion
if previous.hasComposition, state.type != .ofAbortion {
commit(text: previous.displayedText)
}
//
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
clearInlineDisplay()
//
keyHandler.clear()
case .ofCommitting:
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty { commit(text: textToCommit) }
clearInlineDisplay()
//
keyHandler.clear()
case .ofInputting:
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty { commit(text: textToCommit) }
setInlineDisplayWithCursor()
if !state.tooltip.isEmpty {
show(
tooltip: state.tooltip, displayedText: state.displayedText,
u16Cursor: state.data.u16Cursor
)
}
case .ofMarking:
ctlInputMethod.ctlCandidateCurrent.visible = false
setInlineDisplayWithCursor()
if state.tooltip.isEmpty {
ctlInputMethod.tooltipController.hide()
} else {
let cursorReference: Int = {
if state.data.marker >= state.data.cursor { return state.data.u16Cursor }
return state.data.u16Marker //
}()
show(
tooltip: state.tooltip, displayedText: state.displayedText,
u16Cursor: cursorReference
)
}
case .ofCandidates, .ofAssociates, .ofSymbolTable:
ctlInputMethod.tooltipController.hide()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
default: break default: break
} }
} }
@ -48,7 +96,7 @@ extension ctlInputMethod {
/// .NotEmpty() /// .NotEmpty()
func setInlineDisplayWithCursor() { func setInlineDisplayWithCursor() {
guard let client = client() else { return } guard let client = client() else { return }
if let state = state as? InputState.Associates { if state.type == .ofAssociates {
client.setMarkedText( client.setMarkedText(
state.attributedString, selectionRange: NSRange(location: 0, length: 0), state.attributedString, selectionRange: NSRange(location: 0, length: 0),
replacementRange: NSRange(location: NSNotFound, length: NSNotFound) replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
@ -56,49 +104,19 @@ extension ctlInputMethod {
return return
} }
guard let state = state as? InputState.NotEmpty else { if state.hasComposition || state.isCandidateContainer {
clearInlineDisplay() /// selectionRange
/// 0 replacementRangeNSNotFound
///
client.setMarkedText(
state.attributedString, selectionRange: NSRange(location: state.data.u16Cursor, length: 0),
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
return return
} }
var identifier: AnyObject { //
switch IME.currentInputMode { clearInlineDisplay()
case InputMode.imeModeCHS:
if #available(macOS 12.0, *) {
return "zh-Hans" as AnyObject
}
case InputMode.imeModeCHT:
if #available(macOS 12.0, *) {
return (mgrPrefs.shiftJISShinjitaiOutputEnabled || mgrPrefs.chineseConversionEnabled)
? "ja" as AnyObject : "zh-Hant" as AnyObject
}
default:
break
}
return "" as AnyObject
}
// [Shiki's Note] This might needs to be bug-reported to Apple:
// The LanguageIdentifier attribute of an NSAttributeString designated to
// IMK Client().SetMarkedText won't let the actual font respect your languageIdentifier
// settings. Still, this might behaves as Apple's current expectation, I'm afraid.
if #available(macOS 12.0, *) {
state.attributedString.setAttributes(
[.languageIdentifier: identifier],
range: NSRange(
location: 0,
length: state.displayedText.utf16.count
)
)
}
/// selectionRange
/// 0 replacementRangeNSNotFound
///
client.setMarkedText(
state.attributedString, selectionRange: NSRange(location: state.cursorIndex, length: 0),
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
} }
/// .NotEmpty() /// .NotEmpty()
@ -123,109 +141,4 @@ extension ctlInputMethod {
buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound) buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
) )
} }
private func handle(state: InputState.Deactivated, previous: InputStateProtocol) {
_ = state //
ctlInputMethod.ctlCandidateCurrent.delegate = nil
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
if let previous = previous as? InputState.NotEmpty {
commit(text: previous.committingBufferConverted)
}
clearInlineDisplay()
//
keyHandler.clear()
}
private func handle(state: InputState.Empty, previous: InputStateProtocol) {
_ = state //
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
// .Abortion
if let previous = previous as? InputState.NotEmpty,
!(state is InputState.Abortion)
{
commit(text: previous.committingBufferConverted)
}
//
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
clearInlineDisplay()
//
keyHandler.clear()
}
private func handle(
state: InputState.Abortion, previous: InputStateProtocol
) {
_ = state //
_ = previous //
// previous state 使西 commit
handle(state: InputState.Empty())
}
private func handle(state: InputState.Committing, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
clearInlineDisplay()
//
keyHandler.clear()
}
private func handle(state: InputState.Inputting, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
setInlineDisplayWithCursor()
if !state.tooltip.isEmpty {
show(
tooltip: state.tooltip, displayedText: state.displayedText,
cursorIndex: state.cursorIndex
)
}
}
private func handle(state: InputState.Marking, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.ctlCandidateCurrent.visible = false
setInlineDisplayWithCursor()
if state.tooltip.isEmpty {
ctlInputMethod.tooltipController.hide()
} else {
show(
tooltip: state.tooltip, displayedText: state.displayedText,
cursorIndex: state.markerIndex
)
}
}
private func handle(state: InputState.ChoosingCandidate, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.tooltipController.hide()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.SymbolTable, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.tooltipController.hide()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.Associates, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.tooltipController.hide()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
} }

View File

@ -212,9 +212,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Per-Char Select Mode", comment: "") + "\n" message: NSLocalizedString("Per-Char Select Mode", comment: "") + "\n"
+ mgrPrefs.toggleSCPCTypingModeEnabled() + (mgrPrefs.toggleSCPCTypingModeEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -222,9 +222,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Force KangXi Writing", comment: "") + "\n" message: NSLocalizedString("Force KangXi Writing", comment: "") + "\n"
+ mgrPrefs.toggleChineseConversionEnabled() + (mgrPrefs.toggleChineseConversionEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -232,9 +232,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("JIS Shinjitai Output", comment: "") + "\n" message: NSLocalizedString("JIS Shinjitai Output", comment: "") + "\n"
+ mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() + (mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -242,9 +242,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Currency Numeral Output", comment: "") + "\n" message: NSLocalizedString("Currency Numeral Output", comment: "") + "\n"
+ mgrPrefs.toggleCurrencyNumeralsEnabled() + (mgrPrefs.toggleCurrencyNumeralsEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -252,9 +252,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Half-Width Punctuation Mode", comment: "") + "\n" message: NSLocalizedString("Half-Width Punctuation Mode", comment: "") + "\n"
+ mgrPrefs.toggleHalfWidthPunctuationEnabled() + (mgrPrefs.toggleHalfWidthPunctuationEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -262,9 +262,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("CNS11643 Mode", comment: "") + "\n" message: NSLocalizedString("CNS11643 Mode", comment: "") + "\n"
+ mgrPrefs.toggleCNS11643Enabled() + (mgrPrefs.toggleCNS11643Enabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -272,9 +272,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Symbol & Emoji Input", comment: "") + "\n" message: NSLocalizedString("Symbol & Emoji Input", comment: "") + "\n"
+ mgrPrefs.toggleSymbolInputEnabled() + (mgrPrefs.toggleSymbolInputEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -282,9 +282,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Per-Char Associated Phrases", comment: "") + "\n" message: NSLocalizedString("Per-Char Associated Phrases", comment: "") + "\n"
+ mgrPrefs.toggleAssociatedPhrasesEnabled() + (mgrPrefs.toggleAssociatedPhrasesEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }
@ -292,9 +292,9 @@ extension ctlInputMethod {
resetKeyHandler() resetKeyHandler()
NotifierController.notify( NotifierController.notify(
message: NSLocalizedString("Use Phrase Replacement", comment: "") + "\n" message: NSLocalizedString("Use Phrase Replacement", comment: "") + "\n"
+ mgrPrefs.togglePhraseReplacementEnabled() + (mgrPrefs.togglePhraseReplacementEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "") ? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))
) )
} }

View File

@ -18,17 +18,26 @@ public class SymbolNode {
init(_ title: String, _ children: [SymbolNode]? = nil, previous: SymbolNode? = nil) { init(_ title: String, _ children: [SymbolNode]? = nil, previous: SymbolNode? = nil) {
self.title = title self.title = title
self.children = children self.children = children
self.children?.forEach {
$0.previous = self
}
self.previous = previous self.previous = previous
} }
init(_ title: String, symbols: String) { init(_ title: String, symbols: String) {
self.title = title self.title = title
children = Array(symbols).map { SymbolNode(String($0), nil) } children = Array(symbols).map { SymbolNode(String($0), nil) }
children?.forEach {
$0.previous = self
}
} }
init(_ title: String, symbols: [String]) { init(_ title: String, symbols: [String]) {
self.title = title self.title = title
children = symbols.map { SymbolNode($0, nil) } children = symbols.map { SymbolNode($0, nil) }
children?.forEach {
$0.previous = self
}
} }
static func parseUserSymbolNodeData() { static func parseUserSymbolNodeData() {

View File

@ -136,7 +136,6 @@
6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; }; 6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; };
6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; }; 6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; };
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; }; D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; };
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */; }; D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */; };
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* ctlNonModalAlertWindow.swift */; }; D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* ctlNonModalAlertWindow.swift */; };
@ -370,7 +369,6 @@
6ACA41EF15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; 6ACA41EF15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Installer-Info.plist"; path = "Installer/Installer-Info.plist"; sourceTree = SOURCE_ROOT; }; 6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Installer-Info.plist"; path = "Installer/Installer-Info.plist"; sourceTree = SOURCE_ROOT; };
D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppDelegate.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; }; D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppDelegate.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputState.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = main.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; }; D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = main.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; }; D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
D47F7DCF278C0897002F9DD7 /* ctlNonModalAlertWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlNonModalAlertWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; }; D47F7DCF278C0897002F9DD7 /* ctlNonModalAlertWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlNonModalAlertWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
@ -504,7 +502,6 @@
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */, 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
5BF56F9728C39A2700DD6839 /* IMEState.swift */, 5BF56F9728C39A2700DD6839 /* IMEState.swift */,
5BF56F9928C39D1800DD6839 /* IMEStateData.swift */, 5BF56F9928C39D1800DD6839 /* IMEStateData.swift */,
D461B791279DAC010070E734 /* InputState.swift */,
5BD0113C2818543900609769 /* KeyHandler_Core.swift */, 5BD0113C2818543900609769 /* KeyHandler_Core.swift */,
5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */, 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */,
5BE3779F288FED8D0037365B /* KeyHandler_HandleComposition.swift */, 5BE3779F288FED8D0037365B /* KeyHandler_HandleComposition.swift */,
@ -1211,7 +1208,6 @@
5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */, 5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */,
5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */, 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */,
5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */, 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */,
D461B792279DAC010070E734 /* InputState.swift in Sources */,
5BF56F9828C39A2700DD6839 /* IMEState.swift in Sources */, 5BF56F9828C39A2700DD6839 /* IMEState.swift in Sources */,
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */, 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */,
D47B92C027972AD100458394 /* main.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */,