!35 1.6.0 // Bug fixes. Merge Gitee PR!35 from upd/1.6.0
This commit is contained in:
commit
96453d0478
|
@ -2,12 +2,11 @@
|
|||
|
||||
威注音輸入法歡迎有熱心的志願者們參與。
|
||||
|
||||
威注音目前的 codebase 更能代表一個先進的 macOS 輸入法雛形專案的形態。目前的 dev 分支除了 Mandarin 模組(以及其與 KeyHandler 的對接的部分)以外被威注音使用的部分全都是清一色的 Swift codebase,一目了然,方便他人參與,比某些其它開源品牌旗下的專案更具程式方面的生命力。為什麼這樣講呢?那些傳統開源品牌的專案主要使用 C++ 這門不太友好的語言(Mandarin 模組現在對我而言仍舊是天書,一大堆針對記憶體指針的操作完全看不懂。搞不清楚在這一層之上的功能邏輯的話,就無法制定 Swift 版的 coding 策略),這也是我這次用 Swift 重寫了語言模型引擎的原因(也是為後來者行方便)。
|
||||
威注音目前的 codebase 更能代表一個先進的 macOS 輸入法雛形專案的形態。目前的 dev 分支除了第三方 OpenCC 模組以外被威注音使用的部分全都是清一色的 Swift codebase,一目了然,方便他人參與,比某些其它開源品牌旗下的專案更具程式方面的生命力。為什麼這樣講呢?那些傳統開源品牌的專案主要使用 C++ 這門不太友好的語言(Mandarin 模組現在對我而言仍舊是天書,一大堆針對記憶體指針的操作完全看不懂。搞不清楚在這一層之上的功能邏輯的話,就無法制定 Swift 版的 coding 策略),這也是我這次用 Swift 重寫了語言模型引擎與注音拼音並擊處理引擎、來換掉 Gramambular 與 OVMandarin 的原因(也是為後來者行方便)。
|
||||
|
||||
為了不讓參與者們浪費各自的熱情,特設此文以說明該專案目前最需要協助的地方。
|
||||
|
||||
1. 讓 Alt+波浪鍵選單能夠在諸如 MS Word 以及終端機內正常工作(可以用方向鍵控制高亮候選內容,等)。
|
||||
- 原理上而言恐怕得欺騙當前正在接受輸入的應用、使其誤以為當前有組字區。這只是推測。
|
||||
1. 將選字窗換成 IMK 內建的矩陣選字窗。
|
||||
|
||||
除了上述各項以外的貢獻,除非特邀、或者有足夠的說服理由與吸引力(比如語法錯誤或更好的重構方法等),否則敝專案可能會無視或者拒絕。
|
||||
|
||||
|
|
14
README.md
14
README.md
|
@ -7,6 +7,8 @@
|
|||
|
||||
威注音輸入法基於小麥注音二次開發,是**原生簡體中文、原生繁體中文注音輸入法**:
|
||||
|
||||
- 威注音是業界現階段支援注音排列種類數量與輸入用拼音種類數量最多的注音輸入法。
|
||||
- 受威注音自家的鐵恨注音並擊引擎加持。
|
||||
- 威注音的原廠詞庫內不存在任何可以妨礙該輸入法在世界上任何地方傳播的內容。
|
||||
- 相比中州韻(鼠須管)而言,威注音能夠做到真正的大千聲韻並擊。
|
||||
|
||||
|
@ -14,7 +16,7 @@
|
|||
>- 支援 macOS 螢幕模擬鍵盤(僅傳統大千與傳統倚天佈局)。
|
||||
>- 可以將自己打的繁體中文自動轉成日本 JIS 新字體來輸出(包括基礎的字詞轉換)、也可以轉成康熙繁體來輸出。
|
||||
>- 簡繁體中文語料庫彼此分離,徹底杜絕任何繁簡轉換過程可能造成的失誤。
|
||||
>- 支援最新的全字型檔漢字輸入。
|
||||
>- 支援近年的全字庫漢字輸入。
|
||||
>- 可以自動整理使用者語彙檔案格式、自訂聯想詞。
|
||||
>- ……
|
||||
|
||||
|
@ -96,8 +98,14 @@
|
|||
|
||||
請洽該倉庫內的「[CONTRIBUTING.md](./CONTRIBUTING.md)」檔案。
|
||||
|
||||
## 特殊勸告
|
||||
## 其他
|
||||
|
||||
為了您的精神衛生,任何使用威注音輸入法時遇到的產品問題、請勿提報至小麥注音,除非您確信小麥注音也有該問題。即便如此,也請在他們那邊不要提及威注音。
|
||||
|
||||
濫用沉默權來浪費對方的時間與熱情,也是一種暴力。
|
||||
濫用沉默權來浪費對方的時間與熱情,也是一種暴力。**當對方最最最開始就把你當敵人的時候,你連呼吸都是錯的**。
|
||||
|
||||
其實我滿懷念上游專案還沒被 Lukhnos Liu 接管收入 OpenVanilla 的那個年代。MJHsieh 主導開發小麥注音的時候,且不討論他立場怎樣,但基礎的技術交流是完全沒問題的。LibChewing 那邊也是,正常交流完全沒問題。
|
||||
|
||||
有些事情,繼續爭論下去也沒用。本來我想著重寫 ctlInputMethod 撤掉 zonble 的狀態管理引擎的,畢竟有大陸同鄉寫的火山五筆輸入法的框架套上我的鐵恨注音並擊引擎可以直接用。但這樣賭氣對誰都沒好處。眼下,威注音 macOS 版還需要一些小維護。之後我就得開始考慮用 Rust 重寫鐵恨注音並擊引擎與天權星語彙引擎、方便接下來威注音的 Windows 版本的研發。能將 Lukhnos 的 C++ 內容全部換掉、徹底砸碎套在威注音身上的名為 C++ 的枷鎖、讓威注音有一個自由的未來,我已經知足了。讓更多的人用上好用的輸入法,才是最重要的。**這個重要性,不是 zonble 用「私人需求」這種帽子扣過來、就可以泯滅了的**。
|
||||
|
||||
$ EOF.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 4e5f1dc11a9b477b7907f5e59594e636a8830ce8
|
||||
Subproject commit 6c4d659d148ef5be3255427bfd4429fa8690631d
|
|
@ -105,11 +105,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
NSApp.setActivationPolicy(.accessory)
|
||||
}
|
||||
|
||||
func checkForUpdate() {
|
||||
checkForUpdate(forced: false)
|
||||
}
|
||||
|
||||
func checkForUpdate(forced: Bool) {
|
||||
func checkForUpdate(forced: Bool = false) {
|
||||
if checkTask != nil {
|
||||
// busy
|
||||
return
|
||||
|
|
|
@ -117,7 +117,7 @@ enum CharCode: UInt /* 16 */ {
|
|||
// ... but only focuses on which physical key is pressed.
|
||||
}
|
||||
|
||||
class InputHandler: NSObject {
|
||||
struct InputSignal: CustomStringConvertible {
|
||||
private(set) var useVerticalMode: Bool
|
||||
private(set) var inputText: String?
|
||||
private(set) var inputTextIgnoringModifiers: String?
|
||||
|
@ -125,15 +125,15 @@ class InputHandler: NSObject {
|
|||
private(set) var keyCode: UInt16
|
||||
private var isFlagChanged: Bool
|
||||
private var flags: NSEvent.ModifierFlags
|
||||
private var cursorForwardKey: KeyCode
|
||||
private var cursorBackwardKey: KeyCode
|
||||
private var extraChooseCandidateKey: KeyCode
|
||||
private var extraChooseCandidateKeyReverse: KeyCode
|
||||
private var absorbedArrowKey: KeyCode
|
||||
private var verticalModeOnlyChooseCandidateKey: KeyCode
|
||||
private var cursorForwardKey: KeyCode = .kNone
|
||||
private var cursorBackwardKey: KeyCode = .kNone
|
||||
private var extraChooseCandidateKey: KeyCode = .kNone
|
||||
private var extraChooseCandidateKeyReverse: KeyCode = .kNone
|
||||
private var absorbedArrowKey: KeyCode = .kNone
|
||||
private var verticalModeOnlyChooseCandidateKey: KeyCode = .kNone
|
||||
private(set) var emacsKey: vChewingEmacsKey
|
||||
|
||||
init(
|
||||
public init(
|
||||
inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags,
|
||||
isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil
|
||||
) {
|
||||
|
@ -150,17 +150,11 @@ class InputHandler: NSObject {
|
|||
emacsKey = EmacsKeyHelper.detect(
|
||||
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags
|
||||
)
|
||||
// Define Arrow Keys
|
||||
cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow
|
||||
cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow
|
||||
extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow
|
||||
extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow
|
||||
absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow
|
||||
verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone
|
||||
super.init()
|
||||
// Define Arrow Keys in the same way above.
|
||||
defineArrowKeys()
|
||||
}
|
||||
|
||||
init(event: NSEvent, isVerticalMode: Bool) {
|
||||
public init(event: NSEvent, isVerticalMode: Bool) {
|
||||
inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "")
|
||||
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
|
||||
event.charactersIgnoringModifiers ?? "")
|
||||
|
@ -181,22 +175,20 @@ class InputHandler: NSObject {
|
|||
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags
|
||||
)
|
||||
// Define Arrow Keys in the same way above.
|
||||
defineArrowKeys()
|
||||
}
|
||||
|
||||
mutating func defineArrowKeys() {
|
||||
cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow
|
||||
cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow
|
||||
extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow
|
||||
extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow
|
||||
absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow
|
||||
verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone
|
||||
super.init()
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
|
||||
inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "")
|
||||
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
|
||||
inputTextIgnoringModifiers ?? "")
|
||||
return
|
||||
"<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>"
|
||||
var description: String {
|
||||
"<inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>"
|
||||
}
|
||||
|
||||
// 除了 ANSI charCode 以外,其餘一律過濾掉,免得純 Swift 版 KeyHandler 被餵屎。
|
||||
|
@ -365,7 +357,7 @@ enum vChewingEmacsKey: UInt16 {
|
|||
case nextPage = 22 // V
|
||||
}
|
||||
|
||||
class EmacsKeyHelper: NSObject {
|
||||
enum EmacsKeyHelper {
|
||||
static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey {
|
||||
let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
|
||||
if flags.contains(.control) {
|
|
@ -26,6 +26,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
||||
import Cocoa
|
||||
|
||||
// 註:所有 InputState 型別均不適合使用 Struct,因為 Struct 無法相互繼承派生。
|
||||
|
||||
/// Represents the states for the input method controller.
|
||||
///
|
||||
/// An input method is actually a finite state machine. It receives the inputs
|
||||
|
@ -44,10 +46,10 @@ import Cocoa
|
|||
/// create a new state object to replace the current state instead of modifying
|
||||
/// the existing one.
|
||||
///
|
||||
/// vChewing's input controller has following possible states:
|
||||
/// The input controller has following possible states:
|
||||
///
|
||||
/// - Deactivated: The user is not using vChewing yet.
|
||||
/// - Empty: The user has switched to vChewing but did not input anything yet,
|
||||
/// - Deactivated: The user is not using the input method yet.
|
||||
/// - Empty: The user has switched to this input method but inputted nothing yet,
|
||||
/// or, he or she has committed text into the client apps and starts a new
|
||||
/// input phase.
|
||||
/// - Committing: The input controller is sending text to the client apps.
|
||||
|
@ -136,14 +138,14 @@ class InputState {
|
|||
}
|
||||
|
||||
var attributedString: NSAttributedString {
|
||||
let attributedSting = NSAttributedString(
|
||||
let attributedString = NSAttributedString(
|
||||
string: composingBuffer,
|
||||
attributes: [
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0,
|
||||
]
|
||||
)
|
||||
return attributedSting
|
||||
return attributedString
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
|
@ -257,22 +259,22 @@ class InputState {
|
|||
}
|
||||
|
||||
var attributedString: NSAttributedString {
|
||||
let attributedSting = NSMutableAttributedString(string: composingBuffer)
|
||||
let attributedString = NSMutableAttributedString(string: composingBuffer)
|
||||
let end = markedRange.location + markedRange.length
|
||||
|
||||
attributedSting.setAttributes(
|
||||
attributedString.setAttributes(
|
||||
[
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0,
|
||||
], range: NSRange(location: 0, length: markedRange.location)
|
||||
)
|
||||
attributedSting.setAttributes(
|
||||
attributedString.setAttributes(
|
||||
[
|
||||
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
||||
.markedClauseSegment: 1,
|
||||
], range: markedRange
|
||||
)
|
||||
attributedSting.setAttributes(
|
||||
attributedString.setAttributes(
|
||||
[
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 2,
|
||||
|
@ -282,21 +284,21 @@ class InputState {
|
|||
length: (composingBuffer as NSString).length - end
|
||||
)
|
||||
)
|
||||
return attributedSting
|
||||
return attributedString
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
"<InputState.Marking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange)>"
|
||||
}
|
||||
|
||||
func convertToInputting() -> Inputting {
|
||||
var convertedToInputting: Inputting {
|
||||
let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||
state.tooltip = tooltipForInputting
|
||||
return state
|
||||
}
|
||||
|
||||
var validToWrite: Bool {
|
||||
/// vChewing allows users to input a string whose length differs
|
||||
/// The input method allows users to input a string whose length differs
|
||||
/// from the amount of Bopomofo readings. In this case, the range
|
||||
/// in the composing buffer and the readings could not match, so
|
||||
/// we disable the function to write user phrases in this case.
|
||||
|
@ -370,14 +372,14 @@ class InputState {
|
|||
}
|
||||
|
||||
var attributedString: NSAttributedString {
|
||||
let attributedSting = NSAttributedString(
|
||||
let attributedString = NSAttributedString(
|
||||
string: composingBuffer,
|
||||
attributes: [
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0,
|
||||
]
|
||||
)
|
||||
return attributedSting
|
||||
return attributedString
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
|
@ -415,6 +417,22 @@ class InputState {
|
|||
)
|
||||
}
|
||||
|
||||
// InputState.SymbolTable 這個狀態比較特殊,不能把真空組字區交出去。
|
||||
// 不然的話,在絕大多數終端機類應用當中、以及在 MS Word 等軟體當中
|
||||
// 會出現符號選字窗無法響應方向鍵的問題。
|
||||
// 如有誰要修奇摩注音的一點通選單的話,修復原理也是一樣的。
|
||||
// Crediting Qwertyyb: https://github.com/qwertyyb/Fire/issues/55#issuecomment-1133497700
|
||||
override var attributedString: NSAttributedString {
|
||||
let attributedString = NSAttributedString(
|
||||
string: " ",
|
||||
attributes: [
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0,
|
||||
]
|
||||
)
|
||||
return attributedString
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
"<InputState.SymbolTable, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
|
||||
}
|
||||
|
|
|
@ -68,13 +68,35 @@ class KeyHandler {
|
|||
return InputMode.imeModeNULL
|
||||
}
|
||||
}
|
||||
set { setInputMode(newValue.rawValue) }
|
||||
set {
|
||||
let isCHS: Bool = (newValue == InputMode.imeModeCHS)
|
||||
|
||||
// 緊接著將新的簡繁輸入模式提報給 ctlInputMethod:
|
||||
ctlInputMethod.currentInputMode = isCHS ? InputMode.imeModeCHS.rawValue : InputMode.imeModeCHT.rawValue
|
||||
mgrPrefs.mostRecentInputMode = ctlInputMethod.currentInputMode
|
||||
|
||||
// 拿當前的 _inputMode 與 ctlInputMethod 的提報結果對比,不同的話則套用新設定:
|
||||
if _inputMode != ctlInputMethod.currentInputMode {
|
||||
// Reinitiate language models if necessary
|
||||
_languageModel = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT
|
||||
_userOverrideModel = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT
|
||||
|
||||
// Synchronize the sub-languageModel state settings to the new LM.
|
||||
syncBaseLMPrefs()
|
||||
|
||||
// Create new grid builder and clear the composer.
|
||||
createNewBuilder()
|
||||
_composer.clear()
|
||||
}
|
||||
// 直接寫到衛星模組內,省得類型轉換
|
||||
_inputMode = ctlInputMethod.currentInputMode
|
||||
}
|
||||
}
|
||||
|
||||
public init() {
|
||||
_builder = Megrez.BlockReadingBuilder(lm: _languageModel)
|
||||
_builder = Megrez.BlockReadingBuilder(lm: _languageModel, separator: "-")
|
||||
ensureParser()
|
||||
setInputMode(ctlInputMethod.currentInputMode)
|
||||
inputMode = InputMode(rawValue: ctlInputMethod.currentInputMode) ?? InputMode.imeModeNULL
|
||||
}
|
||||
|
||||
func clear() {
|
||||
|
@ -83,34 +105,6 @@ class KeyHandler {
|
|||
_walkedNodes.removeAll()
|
||||
}
|
||||
|
||||
func setInputMode(_ value: String) {
|
||||
// 下面這句的「isKindOfClass」是做類型檢查,
|
||||
// 為了應對出現輸入法 plist 被改壞掉這樣的極端情況。
|
||||
let isCHS: Bool = (value == InputMode.imeModeCHS.rawValue)
|
||||
|
||||
// 緊接著將新的簡繁輸入模式提報給 ctlInputMethod:
|
||||
ctlInputMethod.currentInputMode = isCHS ? InputMode.imeModeCHS.rawValue : InputMode.imeModeCHT.rawValue
|
||||
mgrPrefs.mostRecentInputMode = ctlInputMethod.currentInputMode
|
||||
|
||||
// 拿當前的 _inputMode 與 ctlInputMethod 的提報結果對比,不同的話則套用新設定:
|
||||
if _inputMode != ctlInputMethod.currentInputMode {
|
||||
// Reinitiate language models if necessary
|
||||
setInputModesToLM(isCHS: isCHS)
|
||||
|
||||
// Synchronize the sub-languageModel state settings to the new LM.
|
||||
syncBaseLMPrefs()
|
||||
|
||||
// Create new grid builder.
|
||||
createNewBuilder()
|
||||
|
||||
if !_composer.isEmpty {
|
||||
_composer.clear()
|
||||
}
|
||||
}
|
||||
// 直接寫到衛星模組內,省得類型轉換
|
||||
_inputMode = ctlInputMethod.currentInputMode
|
||||
}
|
||||
|
||||
// MARK: - Functions dealing with Megrez.
|
||||
|
||||
func walk() {
|
||||
|
@ -118,12 +112,10 @@ class KeyHandler {
|
|||
// of the best possible Mandarin characters given the input syllables,
|
||||
// using the Viterbi algorithm implemented in the Megrez library.
|
||||
// The walk() traces the grid to the end, hence no need to use .reversed() here.
|
||||
_walkedNodes = Megrez.Walker(
|
||||
grid: _builder.grid()
|
||||
).walk(at: _builder.grid().width(), nodesLimit: 3, balanced: true)
|
||||
_walkedNodes = _builder.walk(at: _builder.grid.width, nodesLimit: 10, balanced: true)
|
||||
}
|
||||
|
||||
func popOverflowComposingTextAndWalk() -> String {
|
||||
var popOverflowComposingTextAndWalk: String {
|
||||
// In ideal situations we can allow users to type infinitely in a buffer.
|
||||
// However, Viberti algorithm has a complexity of O(N^2), the walk will
|
||||
// become slower as the number of nodes increase. Therefore, we need to
|
||||
|
@ -133,11 +125,11 @@ class KeyHandler {
|
|||
// (i.e. popped out.)
|
||||
|
||||
var poppedText = ""
|
||||
if _builder.grid().width() > mgrPrefs.composingBufferSize {
|
||||
if _builder.grid.width > mgrPrefs.composingBufferSize {
|
||||
if _walkedNodes.count > 0 {
|
||||
let anchor: Megrez.NodeAnchor = _walkedNodes[0]
|
||||
if let theNode = anchor.node {
|
||||
poppedText = theNode.currentKeyValue().value
|
||||
poppedText = theNode.currentKeyValue.value
|
||||
}
|
||||
_builder.removeHeadReadings(count: anchor.spanningLength)
|
||||
}
|
||||
|
@ -155,8 +147,8 @@ class KeyHandler {
|
|||
}
|
||||
|
||||
func fixNode(value: String) {
|
||||
let cursorIndex: Int = getActualCandidateCursorIndex()
|
||||
let selectedNode: Megrez.NodeAnchor = _builder.grid().fixNodeSelectedCandidate(
|
||||
let cursorIndex: Int = actualCandidateCursorIndex
|
||||
let selectedNode: Megrez.NodeAnchor = _builder.grid.fixNodeSelectedCandidate(
|
||||
location: cursorIndex, value: value
|
||||
)
|
||||
// 不要針對逐字選字模式啟用臨時半衰記憶模型。
|
||||
|
@ -194,16 +186,16 @@ class KeyHandler {
|
|||
if nextPosition >= cursorIndex { break }
|
||||
nextPosition += node.spanningLength
|
||||
}
|
||||
if nextPosition <= getBuilderLength() {
|
||||
setBuilderCursorIndex(value: nextPosition)
|
||||
if nextPosition <= builderLength {
|
||||
builderCursorIndex = nextPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCandidatesArray() -> [String] {
|
||||
var candidatesArray: [String] {
|
||||
var arrCandidates: [String] = []
|
||||
var arrNodes: [Megrez.NodeAnchor] = []
|
||||
arrNodes.append(contentsOf: getRawNodes())
|
||||
arrNodes.append(contentsOf: rawNodes)
|
||||
|
||||
/// 原理:nodes 這個回饋結果包含一堆子陣列,分別對應不同詞長的候選字。
|
||||
/// 這裡先對陣列排序、讓最長候選字的子陣列的優先權最高。
|
||||
|
@ -216,7 +208,7 @@ class KeyHandler {
|
|||
// then use the Swift trick to retrieve the candidates for each node at/crossing the cursor
|
||||
for currentNodeAnchor in arrNodes {
|
||||
if let currentNode = currentNodeAnchor.node {
|
||||
for currentCandidate in currentNode.candidates() {
|
||||
for currentCandidate in currentNode.candidates {
|
||||
arrCandidates.append(currentCandidate.value)
|
||||
}
|
||||
}
|
||||
|
@ -230,17 +222,17 @@ class KeyHandler {
|
|||
mgrPrefs.useSCPCTypingMode
|
||||
? ""
|
||||
: _userOverrideModel.suggest(
|
||||
walkedNodes: _walkedNodes, cursorIndex: getBuilderCursorIndex(),
|
||||
walkedNodes: _walkedNodes, cursorIndex: builderCursorIndex,
|
||||
timestamp: NSDate().timeIntervalSince1970
|
||||
)
|
||||
|
||||
if !overrideValue.isEmpty {
|
||||
IME.prtDebugIntel(
|
||||
"UOM: Suggestion retrieved, overriding the node score of the selected candidate.")
|
||||
_builder.grid().overrideNodeScoreForSelectedCandidate(
|
||||
location: getActualCandidateCursorIndex(),
|
||||
_builder.grid.overrideNodeScoreForSelectedCandidate(
|
||||
location: actualCandidateCursorIndex,
|
||||
value: overrideValue,
|
||||
overridingScore: findHighestScore(nodes: getRawNodes(), epsilon: kEpsilon)
|
||||
overridingScore: findHighestScore(nodes: rawNodes, epsilon: kEpsilon)
|
||||
)
|
||||
} else {
|
||||
IME.prtDebugIntel("UOM: Blank suggestion retrieved, dismissing.")
|
||||
|
@ -251,7 +243,7 @@ class KeyHandler {
|
|||
var highestScore: Double = 0
|
||||
for currentAnchor in nodes {
|
||||
if let theNode = currentAnchor.node {
|
||||
let score = theNode.highestUnigramScore()
|
||||
let score = theNode.highestUnigramScore
|
||||
if score > highestScore {
|
||||
highestScore = score
|
||||
}
|
||||
|
@ -260,70 +252,6 @@ class KeyHandler {
|
|||
return highestScore + epsilon
|
||||
}
|
||||
|
||||
// MARK: - Extracted methods and functions (Megrez).
|
||||
|
||||
func isBuilderEmpty() -> Bool { _builder.grid().width() == 0 }
|
||||
|
||||
func getRawNodes() -> [Megrez.NodeAnchor] {
|
||||
/// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。
|
||||
/// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的,但目前 Megrez 暫時缺乏對該特性的支援。
|
||||
/// 所以暫時只能將威注音的游標後置風格描述成「跟 Windows 版雅虎奇摩注音一致」。
|
||||
mgrPrefs.setRearCursorMode
|
||||
? _builder.grid().nodesCrossingOrEndingAt(location: getActualCandidateCursorIndex())
|
||||
: _builder.grid().nodesEndingAt(location: getActualCandidateCursorIndex())
|
||||
}
|
||||
|
||||
func setInputModesToLM(isCHS: Bool) {
|
||||
_languageModel = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT
|
||||
_userOverrideModel = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT
|
||||
}
|
||||
|
||||
func syncBaseLMPrefs() {
|
||||
_languageModel.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled
|
||||
_languageModel.isCNSEnabled = mgrPrefs.cns11643Enabled
|
||||
_languageModel.isSymbolEnabled = mgrPrefs.symbolInputEnabled
|
||||
}
|
||||
|
||||
func createNewBuilder() {
|
||||
_builder = Megrez.BlockReadingBuilder(lm: _languageModel)
|
||||
// Each Mandarin syllable is separated by a hyphen.
|
||||
_builder.setJoinSeparator(separator: "-")
|
||||
}
|
||||
|
||||
func currentReadings() -> [String] { _builder.readings() }
|
||||
|
||||
func ifLangModelHasUnigrams(forKey reading: String) -> Bool {
|
||||
_languageModel.hasUnigramsFor(key: reading)
|
||||
}
|
||||
|
||||
func insertReadingToBuilderAtCursor(reading: String) {
|
||||
_builder.insertReadingAtCursor(reading: reading)
|
||||
}
|
||||
|
||||
func setBuilderCursorIndex(value: Int) {
|
||||
_builder.setCursorIndex(newIndex: value)
|
||||
}
|
||||
|
||||
func getBuilderCursorIndex() -> Int {
|
||||
_builder.cursorIndex()
|
||||
}
|
||||
|
||||
func getBuilderLength() -> Int {
|
||||
_builder.length()
|
||||
}
|
||||
|
||||
func deleteBuilderReadingInFrontOfCursor() {
|
||||
_builder.deleteReadingAtTheRearOfCursor()
|
||||
}
|
||||
|
||||
func deleteBuilderReadingToTheFrontOfCursor() {
|
||||
_builder.deleteReadingToTheFrontOfCursor()
|
||||
}
|
||||
|
||||
func getKeyLengthAtIndexZero() -> Int {
|
||||
_walkedNodes[0].node?.currentKeyValue().value.count ?? 0
|
||||
}
|
||||
|
||||
// MARK: - Extracted methods and functions (Tekkon).
|
||||
|
||||
func ensureParser() {
|
||||
|
@ -360,4 +288,59 @@ class KeyHandler {
|
|||
}
|
||||
_composer.clear()
|
||||
}
|
||||
|
||||
// MARK: - Extracted methods and functions (Megrez).
|
||||
|
||||
var isBuilderEmpty: Bool { _builder.grid.width == 0 }
|
||||
|
||||
var rawNodes: [Megrez.NodeAnchor] {
|
||||
/// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。
|
||||
/// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的,但目前 Megrez 暫時缺乏對該特性的支援。
|
||||
/// 所以暫時只能將威注音的游標後置風格描述成「跟 Windows 版雅虎奇摩注音一致」。
|
||||
mgrPrefs.setRearCursorMode
|
||||
? _builder.grid.nodesCrossingOrEndingAt(location: actualCandidateCursorIndex)
|
||||
: _builder.grid.nodesEndingAt(location: actualCandidateCursorIndex)
|
||||
}
|
||||
|
||||
func syncBaseLMPrefs() {
|
||||
_languageModel.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled
|
||||
_languageModel.isCNSEnabled = mgrPrefs.cns11643Enabled
|
||||
_languageModel.isSymbolEnabled = mgrPrefs.symbolInputEnabled
|
||||
}
|
||||
|
||||
func createNewBuilder() {
|
||||
// Each Mandarin syllable is separated by a hyphen.
|
||||
_builder = Megrez.BlockReadingBuilder(lm: _languageModel, separator: "-")
|
||||
}
|
||||
|
||||
var currentReadings: [String] { _builder.readings }
|
||||
|
||||
func ifLangModelHasUnigrams(forKey reading: String) -> Bool {
|
||||
_languageModel.hasUnigramsFor(key: reading)
|
||||
}
|
||||
|
||||
func insertReadingToBuilderAtCursor(reading: String) {
|
||||
_builder.insertReadingAtCursor(reading: reading)
|
||||
}
|
||||
|
||||
var builderCursorIndex: Int {
|
||||
get { _builder.cursorIndex }
|
||||
set { _builder.cursorIndex = newValue }
|
||||
}
|
||||
|
||||
var builderLength: Int {
|
||||
_builder.length
|
||||
}
|
||||
|
||||
func deleteBuilderReadingInFrontOfCursor() {
|
||||
_builder.deleteReadingAtTheRearOfCursor()
|
||||
}
|
||||
|
||||
func deleteBuilderReadingToTheFrontOfCursor() {
|
||||
_builder.deleteReadingToTheFrontOfCursor()
|
||||
}
|
||||
|
||||
var keyLengthAtIndexZero: Int {
|
||||
_walkedNodes[0].node?.currentKeyValue.value.count ?? 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import Cocoa
|
|||
extension KeyHandler {
|
||||
func handleCandidate(
|
||||
state: InputState,
|
||||
input: InputHandler,
|
||||
input: InputSignal,
|
||||
stateCallback: @escaping (InputState) -> Void,
|
||||
errorCallback: @escaping () -> Void
|
||||
) -> Bool {
|
||||
|
@ -47,16 +47,16 @@ extension KeyHandler {
|
|||
if cancelCandidateKey {
|
||||
if (state is InputState.AssociatedPhrases)
|
||||
|| mgrPrefs.useSCPCTypingMode
|
||||
|| isBuilderEmpty()
|
||||
|| isBuilderEmpty
|
||||
{
|
||||
// 如果此時發現當前組字緩衝區為真空的情況的話,
|
||||
// 就將當前的組字緩衝區析構處理、強制重設輸入狀態。
|
||||
// 否則,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。
|
||||
// 所以這裡需要對 isBuilderEmpty() 做判定。
|
||||
// 所以這裡需要對 isBuilderEmpty 做判定。
|
||||
clear()
|
||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
||||
} else {
|
||||
stateCallback(buildInputtingState())
|
||||
stateCallback(buildInputtingState)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ extension KeyHandler {
|
|||
punctuationNamePrefix = "_punctuation_"
|
||||
}
|
||||
|
||||
let parser = getCurrentMandarinParser()
|
||||
let parser = currentMandarinParser
|
||||
|
||||
let arrCustomPunctuations: [String] = [
|
||||
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),
|
||||
|
|
|
@ -30,7 +30,7 @@ import Cocoa
|
|||
|
||||
extension KeyHandler {
|
||||
func handle(
|
||||
input: InputHandler,
|
||||
input: InputSignal,
|
||||
state: InputState,
|
||||
stateCallback: @escaping (InputState) -> Void,
|
||||
errorCallback: @escaping () -> Void
|
||||
|
@ -135,7 +135,7 @@ extension KeyHandler {
|
|||
) {
|
||||
return true
|
||||
}
|
||||
state = marking.convertToInputting()
|
||||
state = marking.convertedToInputting
|
||||
stateCallback(state)
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ extension KeyHandler {
|
|||
// update the composing buffer.
|
||||
let composeReading = _composer.hasToneMarker()
|
||||
if !composeReading {
|
||||
stateCallback(buildInputtingState())
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ extension KeyHandler {
|
|||
// However, Swift does not support "|=".
|
||||
composeReading = composeReading || (!_composer.isEmpty && (input.isSpace || input.isEnter))
|
||||
if composeReading {
|
||||
if input.isSpace && !_composer.hasToneMarker() {
|
||||
if input.isSpace, !_composer.hasToneMarker() {
|
||||
_composer.receiveKey(fromString: " ") // 補上空格。
|
||||
}
|
||||
let reading = _composer.getComposition()
|
||||
|
@ -176,7 +176,7 @@ extension KeyHandler {
|
|||
IME.prtDebugIntel("B49C0979:語彙庫內無「\(reading)」的匹配記錄。")
|
||||
errorCallback()
|
||||
_composer.clear()
|
||||
stateCallback((getBuilderLength() == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState())
|
||||
stateCallback((builderLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -184,7 +184,7 @@ extension KeyHandler {
|
|||
insertReadingToBuilderAtCursor(reading: reading)
|
||||
|
||||
// ... then walk the grid...
|
||||
let poppedText = popOverflowComposingTextAndWalk()
|
||||
let poppedText = popOverflowComposingTextAndWalk
|
||||
|
||||
// ... get and tweak override model suggestion if possible...
|
||||
dealWithOverrideModelSuggestions()
|
||||
|
@ -192,7 +192,7 @@ extension KeyHandler {
|
|||
// ... then update the text.
|
||||
_composer.clear()
|
||||
|
||||
let inputting = buildInputtingState()
|
||||
let inputting = buildInputtingState
|
||||
inputting.poppedText = poppedText
|
||||
stateCallback(inputting)
|
||||
|
||||
|
@ -233,7 +233,7 @@ extension KeyHandler {
|
|||
// but does not compose. Only sequences such as "ㄧˊ", "ˊㄧˊ", "ˊㄧˇ", or "ˊㄧ "
|
||||
// would compose.
|
||||
if keyConsumedByReading {
|
||||
stateCallback(buildInputtingState())
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,7 @@ extension KeyHandler {
|
|||
if input.isSpace {
|
||||
// If the Space key is NOT set to be a selection key
|
||||
if input.isShiftHold || !mgrPrefs.chooseCandidateUsingSpace {
|
||||
if getBuilderCursorIndex() >= getBuilderLength() {
|
||||
if builderCursorIndex >= builderLength {
|
||||
let composingBuffer = currentState.composingBuffer
|
||||
if !composingBuffer.isEmpty {
|
||||
stateCallback(InputState.Committing(poppedText: composingBuffer))
|
||||
|
@ -257,8 +257,8 @@ extension KeyHandler {
|
|||
stateCallback(InputState.Empty())
|
||||
} else if ifLangModelHasUnigrams(forKey: " ") {
|
||||
insertReadingToBuilderAtCursor(reading: " ")
|
||||
let poppedText = popOverflowComposingTextAndWalk()
|
||||
let inputting = buildInputtingState()
|
||||
let poppedText = popOverflowComposingTextAndWalk
|
||||
let inputting = buildInputtingState
|
||||
inputting.poppedText = poppedText
|
||||
stateCallback(inputting)
|
||||
}
|
||||
|
@ -351,12 +351,12 @@ extension KeyHandler {
|
|||
// MARK: Punctuation list
|
||||
|
||||
if input.isSymbolMenuPhysicalKey && !input.isShiftHold {
|
||||
if !input.isOptionHold {
|
||||
if input.isOptionHold {
|
||||
if ifLangModelHasUnigrams(forKey: "_punctuation_list") {
|
||||
if _composer.isEmpty {
|
||||
insertReadingToBuilderAtCursor(reading: "_punctuation_list")
|
||||
let poppedText: String! = popOverflowComposingTextAndWalk()
|
||||
let inputting = buildInputtingState()
|
||||
let poppedText: String! = popOverflowComposingTextAndWalk
|
||||
let inputting = buildInputtingState
|
||||
inputting.poppedText = poppedText
|
||||
stateCallback(inputting)
|
||||
stateCallback(buildCandidate(state: inputting, useVerticalMode: input.useVerticalMode))
|
||||
|
@ -392,7 +392,7 @@ extension KeyHandler {
|
|||
punctuationNamePrefix = "_punctuation_"
|
||||
}
|
||||
|
||||
let parser = getCurrentMandarinParser()
|
||||
let parser = currentMandarinParser
|
||||
let arrCustomPunctuations: [String] = [
|
||||
punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)),
|
||||
]
|
||||
|
|
|
@ -29,22 +29,22 @@ import Cocoa
|
|||
// MARK: - § Misc functions.
|
||||
|
||||
extension KeyHandler {
|
||||
func getCurrentMandarinParser() -> String {
|
||||
var currentMandarinParser: String {
|
||||
mgrPrefs.mandarinParserName + "_"
|
||||
}
|
||||
|
||||
func getActualCandidateCursorIndex() -> Int {
|
||||
var cursorIndex = getBuilderCursorIndex()
|
||||
var actualCandidateCursorIndex: Int {
|
||||
var cursorIndex = builderCursorIndex
|
||||
// Windows Yahoo Kimo IME style, phrase is *at the rear of* the cursor.
|
||||
// (i.e. the cursor is always *before* the phrase.)
|
||||
// This is different from MS Phonetics IME style ...
|
||||
// ... since Windows Yahoo Kimo allows "node crossing".
|
||||
if (mgrPrefs.setRearCursorMode
|
||||
&& (cursorIndex < getBuilderLength()))
|
||||
&& (cursorIndex < builderLength))
|
||||
|| cursorIndex == 0
|
||||
{
|
||||
if cursorIndex == 0, !mgrPrefs.setRearCursorMode {
|
||||
cursorIndex += getKeyLengthAtIndexZero()
|
||||
cursorIndex += keyLengthAtIndexZero
|
||||
} else {
|
||||
cursorIndex += 1
|
||||
}
|
||||
|
|
|
@ -31,42 +31,47 @@ import Cocoa
|
|||
extension KeyHandler {
|
||||
// MARK: - 構築狀態(State Building)
|
||||
|
||||
func buildInputtingState() -> InputState.Inputting {
|
||||
var buildInputtingState: InputState.Inputting {
|
||||
// "Updating the composing buffer" means to request the client
|
||||
// to "refresh" the text input buffer with our "composing text"
|
||||
var composingBuffer = ""
|
||||
var composedStringCursorIndex = 0
|
||||
|
||||
var readingCursorIndex = 0
|
||||
let builderCursorIndex = getBuilderCursorIndex()
|
||||
|
||||
for theAnchor in _walkedNodes {
|
||||
guard let node = theAnchor.node else {
|
||||
continue
|
||||
}
|
||||
|
||||
let valueString = node.currentKeyValue().value
|
||||
composingBuffer += valueString
|
||||
let codepointCount = valueString.count
|
||||
|
||||
let spanningLength = theAnchor.spanningLength
|
||||
if readingCursorIndex + spanningLength <= builderCursorIndex {
|
||||
composedStringCursorIndex += valueString.count
|
||||
readingCursorIndex += spanningLength
|
||||
} else {
|
||||
if codepointCount == spanningLength {
|
||||
for _ in 0..<codepointCount {
|
||||
if readingCursorIndex < builderCursorIndex {
|
||||
composedStringCursorIndex += 1
|
||||
readingCursorIndex += 1
|
||||
}
|
||||
}
|
||||
// We must do some Unicode codepoint counting to find the actual cursor location for the client
|
||||
// i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars
|
||||
// locations. These processes are inherited from the ObjC++ version of this class and might be
|
||||
// unnecessary in Swift, but this deduction requires further experiments.
|
||||
for walkedNode in _walkedNodes {
|
||||
if let theNode = walkedNode.node {
|
||||
let strNodeValue = theNode.currentKeyValue.value
|
||||
composingBuffer += strNodeValue
|
||||
let arrSplit: [NSString] = (strNodeValue as NSString).split()
|
||||
let codepointCount = arrSplit.count
|
||||
// This re-aligns the cursor index in the composed string
|
||||
// (the actual cursor on the screen) with the builder's logical
|
||||
// cursor (reading) cursor; each built node has a "spanning length"
|
||||
// (e.g. two reading blocks has a spanning length of 2), and we
|
||||
// accumulate those lengths to calculate the displayed cursor
|
||||
// index.
|
||||
let spanningLength: Int = walkedNode.spanningLength
|
||||
if readingCursorIndex + spanningLength <= builderCursorIndex {
|
||||
composedStringCursorIndex += (strNodeValue as NSString).length
|
||||
readingCursorIndex += spanningLength
|
||||
} else {
|
||||
if readingCursorIndex < builderCursorIndex {
|
||||
composedStringCursorIndex += valueString.count
|
||||
readingCursorIndex += spanningLength
|
||||
if readingCursorIndex > builderCursorIndex {
|
||||
readingCursorIndex = builderCursorIndex
|
||||
if codepointCount == spanningLength {
|
||||
var i = 0
|
||||
while i < codepointCount, readingCursorIndex < builderCursorIndex {
|
||||
composedStringCursorIndex += arrSplit[i].length
|
||||
readingCursorIndex += 1
|
||||
i += 1
|
||||
}
|
||||
} else {
|
||||
if readingCursorIndex < builderCursorIndex {
|
||||
composedStringCursorIndex += (strNodeValue as NSString).length
|
||||
readingCursorIndex += spanningLength
|
||||
if readingCursorIndex > builderCursorIndex {
|
||||
readingCursorIndex = builderCursorIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,21 +81,9 @@ extension KeyHandler {
|
|||
// Now, we gather all the intel, separate the composing buffer to two parts (head and tail),
|
||||
// and insert the reading text (the Mandarin syllable) in between them.
|
||||
// The reading text is what the user is typing.
|
||||
|
||||
var rawHead = ""
|
||||
var rawEnd = ""
|
||||
|
||||
for (i, n) in composingBuffer.enumerated() {
|
||||
if i < composedStringCursorIndex {
|
||||
rawHead += String(n)
|
||||
} else {
|
||||
rawEnd += String(n)
|
||||
}
|
||||
}
|
||||
|
||||
let head = rawHead
|
||||
let head = String((composingBuffer as NSString).substring(to: composedStringCursorIndex))
|
||||
let reading = _composer.getInlineCompositionForIMK(isHanyuPinyin: mgrPrefs.showHanyuPinyinInCompositionBuffer)
|
||||
let tail = rawEnd
|
||||
let tail = String((composingBuffer as NSString).substring(from: composedStringCursorIndex))
|
||||
let composedText = head + reading + tail
|
||||
let cursorIndex = composedStringCursorIndex + reading.count
|
||||
|
||||
|
@ -106,7 +99,7 @@ extension KeyHandler {
|
|||
InputState.ChoosingCandidate(
|
||||
composingBuffer: currentState.composingBuffer,
|
||||
cursorIndex: currentState.cursorIndex,
|
||||
candidates: getCandidatesArray(),
|
||||
candidates: candidatesArray,
|
||||
useVerticalMode: useVerticalMode
|
||||
)
|
||||
}
|
||||
|
@ -115,7 +108,7 @@ extension KeyHandler {
|
|||
|
||||
// 這次重寫時,針對「buildAssociatePhraseStateWithKey」這個(用以生成帶有
|
||||
// 聯想詞候選清單的結果的狀態回呼的)函數進行了小幅度的重構處理,使其始終
|
||||
// 可以從 ObjC 部分的「buildAssociatePhraseArray」函數獲取到一個內容類型
|
||||
// 可以從 Core 部分的「buildAssociatePhraseArray」函數獲取到一個內容類型
|
||||
// 為「String」的標準 Swift 陣列。這樣一來,該聯想詞狀態回呼函數將始終能
|
||||
// 夠傳回正確的結果形態、永遠也無法傳回 nil。於是,所有在用到該函數時以
|
||||
// 回傳結果類型判斷作為合法性判斷依據的函數,全都將依據改為檢查傳回的陣列
|
||||
|
@ -134,12 +127,12 @@ extension KeyHandler {
|
|||
|
||||
func handleMarkingState(
|
||||
_ state: InputState.Marking,
|
||||
input: InputHandler,
|
||||
input: InputSignal,
|
||||
stateCallback: @escaping (InputState) -> Void,
|
||||
errorCallback: @escaping () -> Void
|
||||
) -> Bool {
|
||||
if input.isESC {
|
||||
stateCallback(buildInputtingState())
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -152,7 +145,7 @@ extension KeyHandler {
|
|||
return true
|
||||
}
|
||||
}
|
||||
stateCallback(buildInputtingState())
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -168,7 +161,7 @@ extension KeyHandler {
|
|||
readings: state.readings
|
||||
)
|
||||
marking.tooltipForInputting = state.tooltipForInputting
|
||||
stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking)
|
||||
stateCallback(marking.markedRange.length == 0 ? marking.convertedToInputting : marking)
|
||||
} else {
|
||||
IME.prtDebugIntel("1149908D")
|
||||
errorCallback()
|
||||
|
@ -191,7 +184,7 @@ extension KeyHandler {
|
|||
readings: state.readings
|
||||
)
|
||||
marking.tooltipForInputting = state.tooltipForInputting
|
||||
stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking)
|
||||
stateCallback(marking.markedRange.length == 0 ? marking.convertedToInputting : marking)
|
||||
} else {
|
||||
IME.prtDebugIntel("9B51408D")
|
||||
errorCallback()
|
||||
|
@ -217,8 +210,8 @@ extension KeyHandler {
|
|||
|
||||
if _composer.isEmpty {
|
||||
insertReadingToBuilderAtCursor(reading: customPunctuation)
|
||||
let poppedText = popOverflowComposingTextAndWalk()
|
||||
let inputting = buildInputtingState()
|
||||
let poppedText = popOverflowComposingTextAndWalk
|
||||
let inputting = buildInputtingState
|
||||
inputting.poppedText = poppedText
|
||||
stateCallback(inputting)
|
||||
|
||||
|
@ -273,7 +266,7 @@ extension KeyHandler {
|
|||
) -> Bool {
|
||||
guard state is InputState.Inputting else { return false }
|
||||
|
||||
var composingBuffer = currentReadings().joined(separator: "-")
|
||||
var composingBuffer = currentReadings.joined(separator: "-")
|
||||
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
|
||||
composingBuffer = restoreToneOneInZhuyinKey(target: composingBuffer) // 恢復陰平標記
|
||||
composingBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: composingBuffer) // 注音轉拼音
|
||||
|
@ -303,7 +296,7 @@ extension KeyHandler {
|
|||
|
||||
for theAnchor in _walkedNodes {
|
||||
if let node = theAnchor.node {
|
||||
var key = node.currentKeyValue().key
|
||||
var key = node.currentKeyValue.key
|
||||
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
|
||||
key = restoreToneOneInZhuyinKey(target: key) // 恢復陰平標記
|
||||
key = Tekkon.cnvPhonaToHanyuPinyin(target: key) // 注音轉拼音
|
||||
|
@ -313,7 +306,7 @@ extension KeyHandler {
|
|||
key = cnvZhuyinKeyToTextbookReading(target: key, newSeparator: " ")
|
||||
}
|
||||
|
||||
let value = node.currentKeyValue().value
|
||||
let value = node.currentKeyValue.value
|
||||
if key.contains("_") { // 不要給標點符號等特殊元素加注音
|
||||
composed += value
|
||||
} else {
|
||||
|
@ -341,7 +334,7 @@ extension KeyHandler {
|
|||
if _composer.hasToneMarker(withNothingElse: true) {
|
||||
_composer.clear()
|
||||
} else if _composer.isEmpty {
|
||||
if getBuilderCursorIndex() >= 0 {
|
||||
if builderCursorIndex >= 0 {
|
||||
deleteBuilderReadingInFrontOfCursor()
|
||||
walk()
|
||||
} else {
|
||||
|
@ -354,10 +347,10 @@ extension KeyHandler {
|
|||
_composer.doBackSpace()
|
||||
}
|
||||
|
||||
if _composer.isEmpty, getBuilderLength() == 0 {
|
||||
if _composer.isEmpty, builderLength == 0 {
|
||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
||||
} else {
|
||||
stateCallback(buildInputtingState())
|
||||
stateCallback(buildInputtingState)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -372,10 +365,10 @@ extension KeyHandler {
|
|||
guard state is InputState.Inputting else { return false }
|
||||
|
||||
if _composer.isEmpty {
|
||||
if getBuilderCursorIndex() != getBuilderLength() {
|
||||
if builderCursorIndex != builderLength {
|
||||
deleteBuilderReadingToTheFrontOfCursor()
|
||||
walk()
|
||||
let inputting = buildInputtingState()
|
||||
let inputting = buildInputtingState
|
||||
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
||||
if inputting.composingBuffer.isEmpty {
|
||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
||||
|
@ -428,9 +421,9 @@ extension KeyHandler {
|
|||
return true
|
||||
}
|
||||
|
||||
if getBuilderCursorIndex() != 0 {
|
||||
setBuilderCursorIndex(value: 0)
|
||||
stateCallback(buildInputtingState())
|
||||
if builderCursorIndex != 0 {
|
||||
builderCursorIndex = 0
|
||||
stateCallback(buildInputtingState)
|
||||
} else {
|
||||
IME.prtDebugIntel("66D97F90")
|
||||
errorCallback()
|
||||
|
@ -456,9 +449,9 @@ extension KeyHandler {
|
|||
return true
|
||||
}
|
||||
|
||||
if getBuilderCursorIndex() != getBuilderLength() {
|
||||
setBuilderCursorIndex(value: getBuilderLength())
|
||||
stateCallback(buildInputtingState())
|
||||
if builderCursorIndex != builderLength {
|
||||
builderCursorIndex = builderLength
|
||||
stateCallback(buildInputtingState)
|
||||
} else {
|
||||
IME.prtDebugIntel("9B69908E")
|
||||
errorCallback()
|
||||
|
@ -490,10 +483,10 @@ extension KeyHandler {
|
|||
// If reading is not empty, we cancel the reading.
|
||||
if !_composer.isEmpty {
|
||||
_composer.clear()
|
||||
if getBuilderLength() == 0 {
|
||||
if builderLength == 0 {
|
||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
||||
} else {
|
||||
stateCallback(buildInputtingState())
|
||||
stateCallback(buildInputtingState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -504,7 +497,7 @@ extension KeyHandler {
|
|||
|
||||
func handleForward(
|
||||
state: InputState,
|
||||
input: InputHandler,
|
||||
input: InputSignal,
|
||||
stateCallback: @escaping (InputState) -> Void,
|
||||
errorCallback: @escaping () -> Void
|
||||
) -> Bool {
|
||||
|
@ -526,7 +519,7 @@ extension KeyHandler {
|
|||
composingBuffer: currentState.composingBuffer,
|
||||
cursorIndex: currentState.cursorIndex,
|
||||
markerIndex: UInt(nextPosition),
|
||||
readings: currentReadings()
|
||||
readings: currentReadings
|
||||
)
|
||||
marking.tooltipForInputting = currentState.tooltip
|
||||
stateCallback(marking)
|
||||
|
@ -536,9 +529,9 @@ extension KeyHandler {
|
|||
stateCallback(state)
|
||||
}
|
||||
} else {
|
||||
if getBuilderCursorIndex() < getBuilderLength() {
|
||||
setBuilderCursorIndex(value: getBuilderCursorIndex() + 1)
|
||||
stateCallback(buildInputtingState())
|
||||
if builderCursorIndex < builderLength {
|
||||
builderCursorIndex += 1
|
||||
stateCallback(buildInputtingState)
|
||||
} else {
|
||||
IME.prtDebugIntel("A96AAD58")
|
||||
errorCallback()
|
||||
|
@ -553,7 +546,7 @@ extension KeyHandler {
|
|||
|
||||
func handleBackward(
|
||||
state: InputState,
|
||||
input: InputHandler,
|
||||
input: InputSignal,
|
||||
stateCallback: @escaping (InputState) -> Void,
|
||||
errorCallback: @escaping () -> Void
|
||||
) -> Bool {
|
||||
|
@ -575,7 +568,7 @@ extension KeyHandler {
|
|||
composingBuffer: currentState.composingBuffer,
|
||||
cursorIndex: currentState.cursorIndex,
|
||||
markerIndex: UInt(previousPosition),
|
||||
readings: currentReadings()
|
||||
readings: currentReadings
|
||||
)
|
||||
marking.tooltipForInputting = currentState.tooltip
|
||||
stateCallback(marking)
|
||||
|
@ -585,9 +578,9 @@ extension KeyHandler {
|
|||
stateCallback(state)
|
||||
}
|
||||
} else {
|
||||
if getBuilderCursorIndex() > 0 {
|
||||
setBuilderCursorIndex(value: getBuilderCursorIndex() - 1)
|
||||
stateCallback(buildInputtingState())
|
||||
if builderCursorIndex > 0 {
|
||||
builderCursorIndex -= 1
|
||||
stateCallback(buildInputtingState)
|
||||
} else {
|
||||
IME.prtDebugIntel("7045E6F3")
|
||||
errorCallback()
|
||||
|
|
|
@ -96,7 +96,7 @@ public struct Tekkon {
|
|||
]
|
||||
|
||||
/// 引擎僅接受這些記號作為介母
|
||||
public static let allowedsemivowels = ["ㄧ", "ㄨ", "ㄩ"]
|
||||
public static let allowedSemivowels = ["ㄧ", "ㄨ", "ㄩ"]
|
||||
|
||||
/// 引擎僅接受這些記號作為韻母
|
||||
public static let allowedVowels = [
|
||||
|
@ -109,7 +109,7 @@ public struct Tekkon {
|
|||
|
||||
/// 引擎僅接受這些記號作為注音(聲介韻調四個集合加起來)
|
||||
public static var allowedPhonabets: [String] {
|
||||
allowedConsonants + allowedsemivowels + allowedVowels + allowedIntonations
|
||||
allowedConsonants + allowedSemivowels + allowedVowels + allowedIntonations
|
||||
}
|
||||
|
||||
// MARK: - Phonabet Structure
|
||||
|
@ -132,18 +132,7 @@ public struct Tekkon {
|
|||
if !input.isEmpty {
|
||||
if allowedPhonabets.contains(String(input.reversed()[0])) {
|
||||
valueStorage = String(input.reversed()[0])
|
||||
if Tekkon.allowedConsonants.contains(value) {
|
||||
type = .consonant
|
||||
} else if Tekkon.allowedsemivowels.contains(value) {
|
||||
type = .semivowel
|
||||
} else if Tekkon.allowedVowels.contains(value) {
|
||||
type = .vowel
|
||||
} else if Tekkon.allowedIntonations.contains(value) {
|
||||
type = .intonation
|
||||
} else {
|
||||
type = .null
|
||||
valueStorage = ""
|
||||
}
|
||||
ensureType()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,6 +148,23 @@ public struct Tekkon {
|
|||
/// - strWith: 要取代成的內容。
|
||||
mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
||||
valueStorage = valueStorage.replacingOccurrences(of: strOf, with: strWith)
|
||||
ensureType()
|
||||
}
|
||||
|
||||
/// 用來自動更新自身的屬性值的函數。
|
||||
mutating func ensureType() {
|
||||
if Tekkon.allowedConsonants.contains(value) {
|
||||
type = .consonant
|
||||
} else if Tekkon.allowedSemivowels.contains(value) {
|
||||
type = .semivowel
|
||||
} else if Tekkon.allowedVowels.contains(value) {
|
||||
type = .vowel
|
||||
} else if Tekkon.allowedIntonations.contains(value) {
|
||||
type = .intonation
|
||||
} else {
|
||||
type = .null
|
||||
valueStorage = ""
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Misc Definitions
|
||||
|
@ -192,7 +198,7 @@ public struct Tekkon {
|
|||
/// 注音並擊處理的對外介面以注拼槽(Syllable Composer)的形式存在。
|
||||
/// 使用時需要單獨初期化為一個副本變數(因為是 Struct 所以必須得是變數)。
|
||||
/// 注拼槽只有四格:聲、介、韻、調。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 因為是 String Literal,所以初期化時可以藉由 @input 參數指定初期已經傳入的按鍵訊號。
|
||||
/// 還可以在初期化時藉由 @arrange 參數來指定注音排列(預設為「.ofDachen」大千佈局)。
|
||||
@frozen public struct Composer: Equatable, Hashable, ExpressibleByStringLiteral {
|
||||
|
@ -303,7 +309,7 @@ public struct Tekkon {
|
|||
// MARK: - Public Functions
|
||||
|
||||
/// 用於檢測「某個輸入字符訊號的合規性」的函數。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 注意:回傳結果會受到當前注音排列 parser 屬性的影響。
|
||||
/// - Parameters:
|
||||
/// - key: 傳入的 UniChar 內容。
|
||||
|
@ -338,7 +344,7 @@ public struct Tekkon {
|
|||
|
||||
/// 接受傳入的按鍵訊號時的處理,處理對象為 String。
|
||||
/// 另有同名函數可處理 UniChar 訊號。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 如果是諸如複合型注音排列的話,翻譯結果有可能為空,但翻譯過程已經處理好聲介韻調分配了。
|
||||
/// - Parameters:
|
||||
/// - fromString: 傳入的 String 內容。
|
||||
|
@ -364,7 +370,7 @@ public struct Tekkon {
|
|||
|
||||
/// 接受傳入的按鍵訊號時的處理,處理對象為 UniChar。
|
||||
/// 其實也就是先將 UniChar 轉為 String 再交給某個同名異參的函數來處理而已。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 如果是諸如複合型注音排列的話,翻譯結果有可能為空,但翻譯過程已經處理好聲介韻調分配了。
|
||||
/// - Parameters:
|
||||
/// - fromCharCode: 傳入的 UniChar 內容。
|
||||
|
@ -446,7 +452,7 @@ public struct Tekkon {
|
|||
|
||||
/// 專門用來響應使用者摁下 BackSpace 按鍵時的行為。
|
||||
/// 刪除順序:調、韻、介、聲。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 基本上就是按順序從游標前方開始往後刪。
|
||||
public mutating func doBackSpace() {
|
||||
if [.ofHanyuPinyin, .ofSecondaryPinyin, .ofYalePinyin, .ofHualuoPinyin, .ofUniversalPinyin].contains(parser),
|
||||
|
@ -490,7 +496,7 @@ public struct Tekkon {
|
|||
// 注拼槽對內處理用函數都在這一小節。
|
||||
|
||||
/// 根據目前的注音排列設定來翻譯傳入的 String 訊號。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 倚天或許氏鍵盤的處理函數會將分配過程代為處理過,此時回傳結果為空字串。
|
||||
/// - Parameters:
|
||||
/// - key: 傳入的 String 訊號。
|
||||
|
@ -521,7 +527,7 @@ public struct Tekkon {
|
|||
}
|
||||
|
||||
/// 倚天忘形注音排列比較麻煩,需要單獨處理。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 回傳結果是空字串的話,不要緊,因為該函數內部已經處理過分配過程了。
|
||||
/// - Parameters:
|
||||
/// - key: 傳入的 String 訊號。
|
||||
|
@ -605,7 +611,7 @@ public struct Tekkon {
|
|||
}
|
||||
|
||||
/// 許氏鍵盤與倚天忘形一樣同樣也比較麻煩,需要單獨處理。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 回傳結果是空的話,不要緊,因為該函數內部已經處理過分配過程了。
|
||||
/// - Parameters:
|
||||
/// - key: 傳入的 String 訊號。
|
||||
|
@ -726,7 +732,7 @@ public struct Tekkon {
|
|||
}
|
||||
|
||||
/// 大千忘形一樣同樣也比較麻煩,需要單獨處理。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 回傳結果是空的話,不要緊,因為該函數內部已經處理過分配過程了。
|
||||
/// - Parameters:
|
||||
/// - key: 傳入的 String 訊號。
|
||||
|
@ -1237,7 +1243,7 @@ public struct Tekkon {
|
|||
// MARK: - Maps for Keyboard-to-Phonabet parsers
|
||||
|
||||
/// 標準大千排列專用處理陣列。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 威注音輸入法 macOS 版使用了 Ukelele 佈局來完成對諸如倚天傳統等其它注音鍵盤排列的支援。
|
||||
/// 如果要將鐵恨模組拿給別的平台的輸入法使用的話,恐怕需要針對這些注音鍵盤排列各自新增專用陣列才可以。
|
||||
static let mapQwertyDachen: [String: String] = [
|
||||
|
@ -1248,7 +1254,7 @@ public struct Tekkon {
|
|||
]
|
||||
|
||||
/// 大千忘形排列專用處理陣列,但未包含全部的處理內容。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。
|
||||
/// 這裡提前對複音按鍵做處理,然後再用程式判斷介母類型、據此判斷是否需要做複音切換。
|
||||
static let mapDachenCP26StaticKeys: [String: String] = [
|
||||
|
@ -1258,7 +1264,7 @@ public struct Tekkon {
|
|||
]
|
||||
|
||||
/// 許氏排列專用處理陣列,但未包含全部的映射內容。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。
|
||||
/// 這裡提前對複音按鍵做處理,然後再用程式判斷介母類型、據此判斷是否需要做複音切換。
|
||||
static let mapHsuStaticKeys: [String: String] = [
|
||||
|
@ -1268,7 +1274,7 @@ public struct Tekkon {
|
|||
]
|
||||
|
||||
/// 倚天忘形排列預處理專用陣列,但未包含全部的映射內容。
|
||||
/// @--DISCUSSION--@
|
||||
///
|
||||
/// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。
|
||||
/// 這裡提前對ㄓ/ㄍ/ㄕ做處理,然後再用程式判斷介母類型、據此判斷是否需要換成ㄒ/ㄑ/ㄐ。
|
||||
static let mapEten26StaticKeys: [String: String] = [
|
||||
|
|
|
@ -51,7 +51,7 @@ class ctlInputMethod: IMKInputController {
|
|||
|
||||
// 想讓 KeyHandler 能夠被外界調查狀態與參數的話,就得對 KeyHandler 做常態處理。
|
||||
// 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。
|
||||
// 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 InputHandler 無法協同處理。
|
||||
// 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 InputSignal 無法協同處理。
|
||||
// 所以才需要「currentKeyHandler」這個假 KeyHandler。
|
||||
// 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。
|
||||
static var currentKeyHandler: KeyHandler = .init()
|
||||
|
@ -77,9 +77,11 @@ class ctlInputMethod: IMKInputController {
|
|||
|
||||
// MARK: - KeyHandler Reset Command
|
||||
|
||||
func resetKeyHandler() {
|
||||
if let currentClient = currentClient {
|
||||
keyHandler.clear()
|
||||
func resetKeyHandler(client sender: Any? = nil) {
|
||||
keyHandler.clear()
|
||||
if let client = sender as? IMKTextInput {
|
||||
handle(state: InputState.Empty(), client: client)
|
||||
} else if let currentClient = currentClient {
|
||||
handle(state: InputState.Empty(), client: currentClient)
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +114,8 @@ class ctlInputMethod: IMKInputController {
|
|||
handle(state: .Deactivated(), client: client)
|
||||
}
|
||||
|
||||
override func setValue(_ value: Any!, forTag _: Int, client: Any!) {
|
||||
override func setValue(_ value: Any!, forTag tag: Int, client: Any!) {
|
||||
_ = tag // Stop clang-format from ruining the parameters of this function.
|
||||
var newInputMode = InputMode(rawValue: value as? String ?? "") ?? InputMode.imeModeNULL
|
||||
switch newInputMode {
|
||||
case InputMode.imeModeCHS:
|
||||
|
@ -143,7 +146,8 @@ class ctlInputMethod: IMKInputController {
|
|||
|
||||
// MARK: - IMKServerInput protocol methods
|
||||
|
||||
override func recognizedEvents(_: Any!) -> Int {
|
||||
override func recognizedEvents(_ sender: Any!) -> Int {
|
||||
_ = sender // Stop clang-format from ruining the parameters of this function.
|
||||
let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged]
|
||||
return Int(events.rawValue)
|
||||
}
|
||||
|
@ -180,7 +184,7 @@ class ctlInputMethod: IMKInputController {
|
|||
IME.areWeUsingOurOwnPhraseEditor = false
|
||||
}
|
||||
|
||||
let input = InputHandler(event: event, isVerticalMode: useVerticalMode)
|
||||
let input = InputSignal(event: event, isVerticalMode: useVerticalMode)
|
||||
|
||||
// 無法列印的訊號輸入,一概不作處理。
|
||||
// 這個過程不能放在 KeyHandler 內,否則不會起作用。
|
||||
|
@ -195,6 +199,19 @@ class ctlInputMethod: IMKInputController {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 有時會出現某些 App 攔截輸入法的 Ctrl+Enter / Shift+Enter 熱鍵的情況。
|
||||
// 也就是說 handle(event:) 完全抓不到這個 Event。
|
||||
// 這時需要在 commitComposition 這一關做一些收尾處理。
|
||||
override func commitComposition(_ sender: Any!) {
|
||||
resetKeyHandler(client: sender)
|
||||
}
|
||||
|
||||
// 這個函數必須得在對應的狀態下給出對應的內容。
|
||||
override func composedString(_ sender: Any!) -> Any! {
|
||||
_ = sender // Stop clang-format from ruining the parameters of this function.
|
||||
return (state as? InputState.NotEmpty)?.composingBuffer ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - State Handling
|
||||
|
@ -220,6 +237,8 @@ extension ctlInputMethod {
|
|||
handle(state: newState, previous: previous, client: client)
|
||||
} else if let newState = newState as? InputState.AssociatedPhrases {
|
||||
handle(state: newState, previous: previous, client: client)
|
||||
} else if let newState = newState as? InputState.SymbolTable {
|
||||
handle(state: newState, previous: previous, client: client)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,12 +265,25 @@ extension ctlInputMethod {
|
|||
if buffer.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
var bufferOutput = ""
|
||||
|
||||
// 防止輸入法輸出不可列印的字元。
|
||||
for theChar in buffer {
|
||||
if let charCode = theChar.utf16.first {
|
||||
if !(theChar.isASCII && !(charCode.isPrintable())) {
|
||||
bufferOutput += String(theChar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(client as? IMKTextInput)?.insertText(
|
||||
buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||
bufferOutput, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||
)
|
||||
}
|
||||
|
||||
private func handle(state _: InputState.Deactivated, previous: InputState, client: Any?) {
|
||||
private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) {
|
||||
_ = state // Stop clang-format from ruining the parameters of this function.
|
||||
currentClient = nil
|
||||
|
||||
ctlCandidateCurrent?.delegate = nil
|
||||
|
@ -267,7 +299,8 @@ extension ctlInputMethod {
|
|||
)
|
||||
}
|
||||
|
||||
private func handle(state _: InputState.Empty, previous: InputState, client: Any?) {
|
||||
private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
|
||||
_ = state // Stop clang-format from ruining the parameters of this function.
|
||||
ctlCandidateCurrent?.visible = false
|
||||
hideTooltip()
|
||||
|
||||
|
@ -285,8 +318,10 @@ extension ctlInputMethod {
|
|||
}
|
||||
|
||||
private func handle(
|
||||
state _: InputState.EmptyIgnoringPreviousState, previous _: InputState, client: Any!
|
||||
state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any!
|
||||
) {
|
||||
_ = state // Stop clang-format from ruining the parameters of this function.
|
||||
_ = previous // Stop clang-format from ruining the parameters of this function.
|
||||
ctlCandidateCurrent?.visible = false
|
||||
hideTooltip()
|
||||
|
||||
|
@ -300,7 +335,8 @@ extension ctlInputMethod {
|
|||
)
|
||||
}
|
||||
|
||||
private func handle(state: InputState.Committing, previous _: InputState, client: Any?) {
|
||||
private func handle(state: InputState.Committing, previous: InputState, client: Any?) {
|
||||
_ = previous // Stop clang-format from ruining the parameters of this function.
|
||||
ctlCandidateCurrent?.visible = false
|
||||
hideTooltip()
|
||||
|
||||
|
@ -318,7 +354,8 @@ extension ctlInputMethod {
|
|||
)
|
||||
}
|
||||
|
||||
private func handle(state: InputState.Inputting, previous _: InputState, client: Any?) {
|
||||
private func handle(state: InputState.Inputting, previous: InputState, client: Any?) {
|
||||
_ = previous // Stop clang-format from ruining the parameters of this function.
|
||||
ctlCandidateCurrent?.visible = false
|
||||
hideTooltip()
|
||||
|
||||
|
@ -345,7 +382,8 @@ extension ctlInputMethod {
|
|||
}
|
||||
}
|
||||
|
||||
private func handle(state: InputState.Marking, previous _: InputState, client: Any?) {
|
||||
private func handle(state: InputState.Marking, previous: InputState, client: Any?) {
|
||||
_ = previous // Stop clang-format from ruining the parameters of this function.
|
||||
ctlCandidateCurrent?.visible = false
|
||||
guard let client = client as? IMKTextInput else {
|
||||
hideTooltip()
|
||||
|
@ -369,7 +407,8 @@ extension ctlInputMethod {
|
|||
}
|
||||
}
|
||||
|
||||
private func handle(state: InputState.ChoosingCandidate, previous _: InputState, client: Any?) {
|
||||
private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) {
|
||||
_ = previous // Stop clang-format from ruining the parameters of this function.
|
||||
hideTooltip()
|
||||
guard let client = client as? IMKTextInput else {
|
||||
ctlCandidateCurrent?.visible = false
|
||||
|
@ -385,7 +424,25 @@ extension ctlInputMethod {
|
|||
show(candidateWindowWith: state, client: client)
|
||||
}
|
||||
|
||||
private func handle(state: InputState.AssociatedPhrases, previous _: InputState, client: Any?) {
|
||||
private func handle(state: InputState.SymbolTable, previous: InputState, client: Any?) {
|
||||
_ = previous // Stop clang-format from ruining the parameters of this function.
|
||||
hideTooltip()
|
||||
guard let client = client as? IMKTextInput else {
|
||||
ctlCandidateCurrent?.visible = false
|
||||
return
|
||||
}
|
||||
|
||||
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
|
||||
// i.e. the client app needs to take care of where to put this composing buffer
|
||||
client.setMarkedText(
|
||||
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||
)
|
||||
show(candidateWindowWith: state, client: client)
|
||||
}
|
||||
|
||||
private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) {
|
||||
_ = previous // Stop clang-format from ruining the parameters of this function.
|
||||
hideTooltip()
|
||||
guard let client = client as? IMKTextInput else {
|
||||
ctlCandidateCurrent?.visible = false
|
||||
|
@ -547,14 +604,16 @@ extension ctlInputMethod {
|
|||
// MARK: -
|
||||
|
||||
extension ctlInputMethod: KeyHandlerDelegate {
|
||||
func ctlCandidate(for _: KeyHandler) -> Any {
|
||||
ctlCandidateCurrent ?? .vertical
|
||||
func ctlCandidate(for keyHandler: KeyHandler) -> Any {
|
||||
_ = keyHandler // Stop clang-format from ruining the parameters of this function.
|
||||
return ctlCandidateCurrent ?? .vertical
|
||||
}
|
||||
|
||||
func keyHandler(
|
||||
_: KeyHandler, didSelectCandidateAt index: Int,
|
||||
_ keyHandler: KeyHandler, didSelectCandidateAt index: Int,
|
||||
ctlCandidate controller: Any
|
||||
) {
|
||||
_ = keyHandler // Stop clang-format from ruining the parameters of this function.
|
||||
if let controller = controller as? ctlCandidate {
|
||||
ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index))
|
||||
}
|
||||
|
@ -592,7 +651,8 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
|||
// MARK: -
|
||||
|
||||
extension ctlInputMethod: ctlCandidateDelegate {
|
||||
func candidateCountForController(_: ctlCandidate) -> UInt {
|
||||
func candidateCountForController(_ controller: ctlCandidate) -> UInt {
|
||||
_ = controller // Stop clang-format from ruining the parameters of this function.
|
||||
if let state = state as? InputState.ChoosingCandidate {
|
||||
return UInt(state.candidates.count)
|
||||
} else if let state = state as? InputState.AssociatedPhrases {
|
||||
|
@ -601,9 +661,10 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
|||
return 0
|
||||
}
|
||||
|
||||
func ctlCandidate(_: ctlCandidate, candidateAtIndex index: UInt)
|
||||
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt)
|
||||
-> String
|
||||
{
|
||||
_ = controller // Stop clang-format from ruining the parameters of this function.
|
||||
if let state = state as? InputState.ChoosingCandidate {
|
||||
return state.candidates[Int(index)]
|
||||
} else if let state = state as? InputState.AssociatedPhrases {
|
||||
|
@ -612,7 +673,8 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
|||
return ""
|
||||
}
|
||||
|
||||
func ctlCandidate(_: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
|
||||
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
|
||||
_ = controller // Stop clang-format from ruining the parameters of this function.
|
||||
let client = currentClient
|
||||
|
||||
if let state = state as? InputState.SymbolTable,
|
||||
|
@ -634,7 +696,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
|||
let selectedValue = state.candidates[Int(index)]
|
||||
keyHandler.fixNode(value: selectedValue)
|
||||
|
||||
let inputting = keyHandler.buildInputtingState()
|
||||
let inputting = keyHandler.buildInputtingState
|
||||
|
||||
if mgrPrefs.useSCPCTypingMode {
|
||||
keyHandler.clear()
|
||||
|
|
|
@ -302,7 +302,7 @@ extension ctlInputMethod {
|
|||
}
|
||||
|
||||
@objc func openUserDataFolder(_: Any?) {
|
||||
if !mgrLangModel.checkIfUserDataFolderExists() {
|
||||
if !mgrLangModel.userDataFolderExists {
|
||||
return
|
||||
}
|
||||
NSWorkspace.shared.openFile(
|
||||
|
|
|
@ -186,7 +186,7 @@ extension vChewing {
|
|||
var strPrevious = "()"
|
||||
var strAnterior = "()"
|
||||
|
||||
guard let kvCurrent = arrNodesReversed[0].node?.currentKeyValue(),
|
||||
guard let kvCurrent = arrNodesReversed[0].node?.currentKeyValue,
|
||||
!arrEndingPunctuation.contains(kvCurrent.value)
|
||||
else {
|
||||
return ""
|
||||
|
@ -196,14 +196,14 @@ extension vChewing {
|
|||
strCurrent = kvCurrent.key
|
||||
|
||||
if arrNodesReversed.count >= 2,
|
||||
let kvPrevious = arrNodesReversed[1].node?.currentKeyValue(),
|
||||
let kvPrevious = arrNodesReversed[1].node?.currentKeyValue,
|
||||
!arrEndingPunctuation.contains(kvPrevious.value)
|
||||
{
|
||||
strPrevious = "(\(kvPrevious.key),\(kvPrevious.value))"
|
||||
}
|
||||
|
||||
if arrNodesReversed.count >= 3,
|
||||
let kvAnterior = arrNodesReversed[2].node?.currentKeyValue(),
|
||||
let kvAnterior = arrNodesReversed[2].node?.currentKeyValue,
|
||||
!arrEndingPunctuation.contains(kvAnterior.value)
|
||||
{
|
||||
strAnterior = "(\(kvAnterior.key),\(kvAnterior.value))"
|
||||
|
|
|
@ -271,7 +271,7 @@ enum mgrLangModel {
|
|||
}
|
||||
|
||||
static func chkUserLMFilesExist(_ mode: InputMode) -> Bool {
|
||||
if !checkIfUserDataFolderExists() {
|
||||
if !userDataFolderExists {
|
||||
return false
|
||||
}
|
||||
if !ensureFileExists(userPhrasesDataPath(mode))
|
||||
|
@ -309,9 +309,8 @@ enum mgrLangModel {
|
|||
return true
|
||||
}
|
||||
|
||||
// ⚠︎ 私有函數:檢查且糾偏,不接受任何傳入變數。該函數不用於其他型別。
|
||||
// 待辦事項:擇日合併至另一個同類型的函數當中。
|
||||
static func checkIfUserDataFolderExists() -> Bool {
|
||||
// 檢查給定的目錄是否存在寫入合規性、且糾偏,不接受任何傳入變數。
|
||||
static var userDataFolderExists: Bool {
|
||||
let folderPath = mgrLangModel.dataFolderPath(isDefaultFolder: false)
|
||||
var isFolder = ObjCBool(false)
|
||||
var folderExist = FileManager.default.fileExists(atPath: folderPath, isDirectory: &isFolder)
|
||||
|
|
|
@ -24,33 +24,55 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
/// 分節讀音槽。
|
||||
public class BlockReadingBuilder {
|
||||
var mutMaximumBuildSpanLength = 10
|
||||
var mutCursorIndex: Int = 0
|
||||
var mutReadings: [String] = []
|
||||
var mutGrid: Grid = .init()
|
||||
var mutLM: LanguageModel
|
||||
var mutJoinSeparator: String = ""
|
||||
/// 該分節讀音曹內可以允許的最大詞長。
|
||||
private var mutMaximumBuildSpanLength = 10
|
||||
/// 該分節讀音槽的游標位置。
|
||||
private var mutCursorIndex: Int = 0
|
||||
/// 該分節讀音槽的讀音陣列。
|
||||
private var mutReadings: [String] = []
|
||||
/// 該分節讀音槽的軌格。
|
||||
private var mutGrid: Grid = .init()
|
||||
/// 該分節讀音槽所使用的語言模型。
|
||||
private var mutLM: LanguageModel
|
||||
|
||||
public init(lm: LanguageModel, length: Int = 10) {
|
||||
mutLM = lm
|
||||
mutMaximumBuildSpanLength = length
|
||||
/// 公開:多字讀音鍵當中用以分割漢字讀音的記號,預設為空。
|
||||
public var joinSeparator: String = ""
|
||||
/// 公開:該分節讀音槽的游標位置。
|
||||
public var cursorIndex: Int {
|
||||
get { mutCursorIndex }
|
||||
set { mutCursorIndex = (newValue < 0) ? 0 : min(newValue, mutReadings.count) }
|
||||
}
|
||||
|
||||
/// 公開:該分節讀音槽的軌格(唯讀)。
|
||||
public var grid: Grid { mutGrid }
|
||||
/// 公開:該分節讀音槽的長度,也就是內建漢字讀音的數量(唯讀)。
|
||||
public var length: Int { mutReadings.count }
|
||||
/// 公開:該分節讀音槽的讀音陣列(唯讀)。
|
||||
public var readings: [String] { mutReadings }
|
||||
|
||||
/// 分節讀音槽。
|
||||
/// - Parameters:
|
||||
/// - lm: 語言模型。可以是任何基於 Megrez.LanguageModel 的衍生型別。
|
||||
/// - length: 指定該分節讀音曹內可以允許的最大詞長,預設為 10 字。
|
||||
/// - separator: 多字讀音鍵當中用以分割漢字讀音的記號,預設為空。
|
||||
public init(lm: LanguageModel, length: Int = 10, separator: String = "") {
|
||||
mutLM = lm
|
||||
mutMaximumBuildSpanLength = length
|
||||
joinSeparator = separator
|
||||
}
|
||||
|
||||
/// 分節讀音槽自我清空專用函數。
|
||||
public func clear() {
|
||||
mutCursorIndex = 0
|
||||
mutReadings.removeAll()
|
||||
mutGrid.clear()
|
||||
}
|
||||
|
||||
public func length() -> Int { mutReadings.count }
|
||||
|
||||
public func cursorIndex() -> Int { mutCursorIndex }
|
||||
|
||||
public func setCursorIndex(newIndex: Int) {
|
||||
mutCursorIndex = min(newIndex, mutReadings.count)
|
||||
}
|
||||
|
||||
/// 在游標位置插入給定的讀音。
|
||||
/// - Parameters:
|
||||
/// - reading: 要插入的讀音。
|
||||
public func insertReadingAtCursor(reading: String) {
|
||||
mutReadings.insert(reading, at: mutCursorIndex)
|
||||
mutGrid.expandGridByOneAt(location: mutCursorIndex)
|
||||
|
@ -58,8 +80,8 @@ extension Megrez {
|
|||
mutCursorIndex += 1
|
||||
}
|
||||
|
||||
public func readings() -> [String] { mutReadings }
|
||||
|
||||
/// 朝著與文字輸入方向相反的方向、砍掉一個與游標相鄰的讀音。
|
||||
/// 在威注音的術語體系當中,「與文字輸入方向相反的方向」為向後(Rear)。
|
||||
@discardableResult public func deleteReadingAtTheRearOfCursor() -> Bool {
|
||||
if mutCursorIndex == 0 {
|
||||
return false
|
||||
|
@ -72,6 +94,8 @@ extension Megrez {
|
|||
return true
|
||||
}
|
||||
|
||||
/// 朝著往文字輸入方向、砍掉一個與游標相鄰的讀音。
|
||||
/// 在威注音的術語體系當中,「文字輸入方向」為向前(Front)。
|
||||
@discardableResult public func deleteReadingToTheFrontOfCursor() -> Bool {
|
||||
if mutCursorIndex == mutReadings.count {
|
||||
return false
|
||||
|
@ -83,8 +107,12 @@ extension Megrez {
|
|||
return true
|
||||
}
|
||||
|
||||
/// 移除該分節讀音槽的第一個讀音單元。
|
||||
///
|
||||
/// 用於輸入法組字區長度上限處理:
|
||||
/// 將該位置要溢出的敲字內容遞交之後、再執行這個函數。
|
||||
@discardableResult public func removeHeadReadings(count: Int) -> Bool {
|
||||
if count > length() {
|
||||
if count > length {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -100,17 +128,108 @@ extension Megrez {
|
|||
return true
|
||||
}
|
||||
|
||||
public func setJoinSeparator(separator: String) {
|
||||
mutJoinSeparator = separator
|
||||
// MARK: - Walker
|
||||
|
||||
/// 對已給定的軌格按照給定的位置與條件進行正向爬軌。
|
||||
///
|
||||
/// 其實就是將反向爬軌的結果顛倒順序再給出來而已,省得使用者自己再顛倒一遍。
|
||||
/// - Parameters:
|
||||
/// - at: 開始爬軌的位置。
|
||||
/// - score: 給定累計權重,非必填參數。預設值為 0。
|
||||
/// - nodesLimit: 限定最多只爬多少個節點。
|
||||
/// - balanced: 啟用平衡權重,在節點權重的基礎上根據節點幅位長度來加權。
|
||||
public func walk(
|
||||
at location: Int,
|
||||
score accumulatedScore: Double = 0.0,
|
||||
nodesLimit: Int = 0,
|
||||
balanced: Bool = false
|
||||
) -> [NodeAnchor] {
|
||||
Array(
|
||||
reverseWalk(
|
||||
at: location, score: accumulatedScore,
|
||||
nodesLimit: nodesLimit, balanced: balanced
|
||||
).reversed())
|
||||
}
|
||||
|
||||
public func joinSeparator() -> String { mutJoinSeparator }
|
||||
/// 對已給定的軌格按照給定的位置與條件進行反向爬軌。
|
||||
/// - Parameters:
|
||||
/// - at: 開始爬軌的位置。
|
||||
/// - score: 給定累計權重,非必填參數。預設值為 0。
|
||||
/// - nodesLimit: 限定最多只爬多少個節點。
|
||||
/// - balanced: 啟用平衡權重,在節點權重的基礎上根據節點幅位長度來加權。
|
||||
public func reverseWalk(
|
||||
at location: Int,
|
||||
score accumulatedScore: Double = 0.0,
|
||||
nodesLimit: Int = 0,
|
||||
balanced: Bool = false
|
||||
) -> [NodeAnchor] {
|
||||
if location == 0 || location > mutGrid.width {
|
||||
return [] as [NodeAnchor]
|
||||
}
|
||||
|
||||
public func grid() -> Grid { mutGrid }
|
||||
var paths: [[NodeAnchor]] = []
|
||||
var nodes: [NodeAnchor] = mutGrid.nodesEndingAt(location: location)
|
||||
|
||||
public func build() {
|
||||
// if (mutLM == nil) { return } // 這個出不了 nil,所以註釋掉。
|
||||
if balanced {
|
||||
nodes.sort {
|
||||
$0.balancedScore > $1.balancedScore
|
||||
}
|
||||
}
|
||||
|
||||
for (i, n) in nodes.enumerated() {
|
||||
// 只檢查前 X 個 NodeAnchor 是否有 node。
|
||||
// 這裡有 abs 是為了防止有白癡填負數。
|
||||
if abs(nodesLimit) > 0, i == abs(nodesLimit) - 1 {
|
||||
break
|
||||
}
|
||||
|
||||
var n = n
|
||||
guard let nNode = n.node else {
|
||||
continue
|
||||
}
|
||||
|
||||
n.accumulatedScore = accumulatedScore + nNode.score
|
||||
|
||||
// 利用幅位長度來決定權重。
|
||||
// 這樣一來,例:「再見」比「在」與「見」的權重更高。
|
||||
if balanced {
|
||||
let weightedScore: Double = (Double(n.spanningLength) - 1) * 2
|
||||
n.accumulatedScore += weightedScore
|
||||
}
|
||||
|
||||
var path: [NodeAnchor] = reverseWalk(
|
||||
at: location - n.spanningLength,
|
||||
score: n.accumulatedScore
|
||||
)
|
||||
|
||||
path.insert(n, at: 0)
|
||||
|
||||
paths.append(path)
|
||||
|
||||
// 始終使用固定的候選字詞
|
||||
if balanced, nNode.score >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !paths.isEmpty {
|
||||
if var result = paths.first {
|
||||
for value in paths {
|
||||
if let vLast = value.last, let rLast = result.last {
|
||||
if vLast.accumulatedScore > rLast.accumulatedScore {
|
||||
result = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return [] as [NodeAnchor]
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
private func build() {
|
||||
let itrBegin: Int =
|
||||
(mutCursorIndex < mutMaximumBuildSpanLength) ? 0 : mutCursorIndex - mutMaximumBuildSpanLength
|
||||
let itrEnd: Int = min(mutCursorIndex + mutMaximumBuildSpanLength, mutReadings.count)
|
||||
|
@ -121,7 +240,7 @@ extension Megrez {
|
|||
break
|
||||
}
|
||||
let strSlice = mutReadings[p..<(p + q)]
|
||||
let combinedReading: String = join(slice: strSlice, separator: mutJoinSeparator)
|
||||
let combinedReading: String = join(slice: strSlice, separator: joinSeparator)
|
||||
|
||||
if !mutGrid.hasMatchedNode(location: p, spanningLength: q, key: combinedReading) {
|
||||
let unigrams: [Unigram] = mutLM.unigramsFor(key: combinedReading)
|
||||
|
@ -134,7 +253,7 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
|
||||
public func join(slice strSlice: ArraySlice<String>, separator: String) -> String {
|
||||
private func join(slice strSlice: ArraySlice<String>, separator: String) -> String {
|
||||
var arrResult: [String] = []
|
||||
for value in strSlice {
|
||||
arrResult.append(value)
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
|
||||
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. 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 above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
extension Megrez {
|
||||
public class Walker {
|
||||
var mutGrid: Grid
|
||||
|
||||
public init(grid: Megrez.Grid = Megrez.Grid()) {
|
||||
mutGrid = grid
|
||||
}
|
||||
|
||||
public func walk(
|
||||
at location: Int,
|
||||
score accumulatedScore: Double = 0.0,
|
||||
nodesLimit: Int = 0,
|
||||
balanced: Bool = false
|
||||
) -> [NodeAnchor] {
|
||||
var arrReturn: [NodeAnchor] = []
|
||||
let arrReversedSource = reverseWalk(
|
||||
at: location, score: accumulatedScore,
|
||||
nodesLimit: nodesLimit, balanced: balanced
|
||||
).reversed()
|
||||
|
||||
for neta in arrReversedSource {
|
||||
arrReturn.append(neta)
|
||||
}
|
||||
|
||||
return arrReturn
|
||||
}
|
||||
|
||||
public func reverseWalk(
|
||||
at location: Int,
|
||||
score accumulatedScore: Double = 0.0,
|
||||
nodesLimit: Int = 0,
|
||||
balanced: Bool = false
|
||||
) -> [NodeAnchor] {
|
||||
if location == 0 || location > mutGrid.width() {
|
||||
return [] as [NodeAnchor]
|
||||
}
|
||||
|
||||
var paths: [[NodeAnchor]] = []
|
||||
var nodes: [NodeAnchor] = mutGrid.nodesEndingAt(location: location)
|
||||
|
||||
if balanced {
|
||||
nodes.sort {
|
||||
$0.balancedScore > $1.balancedScore
|
||||
}
|
||||
}
|
||||
|
||||
for (i, n) in nodes.enumerated() {
|
||||
// 只檢查前 X 個 NodeAnchor 是否有 node。
|
||||
// 這裡有 abs 是為了防止有白癡填負數。
|
||||
if abs(nodesLimit) > 0, i == abs(nodesLimit) - 1 {
|
||||
break
|
||||
}
|
||||
|
||||
var n = n
|
||||
guard let nNode = n.node else {
|
||||
continue
|
||||
}
|
||||
|
||||
n.accumulatedScore = accumulatedScore + nNode.score()
|
||||
|
||||
// 利用 Spanning Length 來決定權重。
|
||||
// 這樣一來,例:「再見」比「在」與「見」的權重更高。
|
||||
if balanced {
|
||||
let weightedScore: Double = (Double(n.spanningLength) - 1) * 2
|
||||
n.accumulatedScore += weightedScore
|
||||
}
|
||||
|
||||
var path: [NodeAnchor] = reverseWalk(
|
||||
at: location - n.spanningLength,
|
||||
score: n.accumulatedScore
|
||||
)
|
||||
|
||||
path.insert(n, at: 0)
|
||||
|
||||
paths.append(path)
|
||||
|
||||
// 始終使用固定的候選字
|
||||
if balanced, nNode.score() >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !paths.isEmpty {
|
||||
if var result = paths.first {
|
||||
for value in paths {
|
||||
if let vLast = value.last, let rLast = result.last {
|
||||
if vLast.accumulatedScore > rLast.accumulatedScore {
|
||||
result = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return [] as [NodeAnchor]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,17 +24,28 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
/// 軌格。
|
||||
public class Grid {
|
||||
var mutSpans: [Megrez.Span]
|
||||
/// 幅位陣列。
|
||||
private var mutSpans: [Megrez.Span]
|
||||
|
||||
/// 軌格的寬度,也就是其內的幅位陣列當中的幅位數量。
|
||||
var width: Int { mutSpans.count }
|
||||
|
||||
public init() {
|
||||
mutSpans = [Megrez.Span]()
|
||||
}
|
||||
|
||||
/// 自我清空該軌格的內容。
|
||||
public func clear() {
|
||||
mutSpans = [Megrez.Span]()
|
||||
}
|
||||
|
||||
/// 往該軌格的指定位置插入指定幅位長度的指定節點。
|
||||
/// - Parameters:
|
||||
/// - node: 節點。
|
||||
/// - location: 位置。
|
||||
/// - spanningLength: 給定的幅位長度。
|
||||
public func insertNode(node: Node, location: Int, spanningLength: Int) {
|
||||
if location >= mutSpans.count {
|
||||
let diff = location - mutSpans.count + 1
|
||||
|
@ -45,15 +56,23 @@ extension Megrez {
|
|||
mutSpans[location].insert(node: node, length: spanningLength)
|
||||
}
|
||||
|
||||
/// 給定索引鍵、位置、幅位長度,在該軌格內確認是否有對應的節點存在。
|
||||
/// - Parameters:
|
||||
/// - location: 位置。
|
||||
/// - spanningLength: 給定的幅位長度。
|
||||
/// - key: 索引鍵。
|
||||
public func hasMatchedNode(location: Int, spanningLength: Int, key: String) -> Bool {
|
||||
if location > mutSpans.count {
|
||||
return false
|
||||
}
|
||||
|
||||
let n = mutSpans[location].node(length: spanningLength)
|
||||
return n == nil ? false : key == n?.key()
|
||||
return n == nil ? false : key == n?.key
|
||||
}
|
||||
|
||||
/// 在該軌格的指定位置擴增一個幅位。
|
||||
/// - Parameters:
|
||||
/// - location: 位置。
|
||||
public func expandGridByOneAt(location: Int) {
|
||||
// 這裡加入 abs 完全是一個防呆設計
|
||||
mutSpans.insert(Span(), at: abs(location))
|
||||
|
@ -65,6 +84,9 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
|
||||
/// 在該軌格的指定位置減少一個幅位。
|
||||
/// - Parameters:
|
||||
/// - location: 位置。
|
||||
public func shrinkGridByOneAt(location: Int) {
|
||||
if location >= mutSpans.count {
|
||||
return
|
||||
|
@ -77,8 +99,9 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
|
||||
public func width() -> Int { mutSpans.count }
|
||||
|
||||
/// 給定位置,枚舉出所有在這個位置結尾的節點。
|
||||
/// - Parameters:
|
||||
/// - location: 位置。
|
||||
public func nodesEndingAt(location: Int) -> [NodeAnchor] {
|
||||
var results: [NodeAnchor] = []
|
||||
if !mutSpans.isEmpty, location <= mutSpans.count {
|
||||
|
@ -100,6 +123,9 @@ extension Megrez {
|
|||
return results
|
||||
}
|
||||
|
||||
/// 給定位置,枚舉出所有在這個位置結尾、或者橫跨該位置的節點。
|
||||
/// - Parameters:
|
||||
/// - location: 位置。
|
||||
public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] {
|
||||
var results: [NodeAnchor] = []
|
||||
if !mutSpans.isEmpty, location <= mutSpans.count {
|
||||
|
@ -126,14 +152,18 @@ extension Megrez {
|
|||
return results
|
||||
}
|
||||
|
||||
public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor {
|
||||
/// 將給定位置的節點的候選字詞改為與給定的字串一致的候選字詞。
|
||||
/// - Parameters:
|
||||
/// - location: 位置。
|
||||
/// - value: 給定字串。
|
||||
@discardableResult public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor {
|
||||
var node = NodeAnchor()
|
||||
for nodeAnchor in nodesCrossingOrEndingAt(location: location) {
|
||||
guard let theNode = nodeAnchor.node else {
|
||||
continue
|
||||
}
|
||||
let candidates = theNode.candidates()
|
||||
// Reset the candidate-fixed state of every node at the location.
|
||||
let candidates = theNode.candidates
|
||||
// 將該位置的所有節點的候選字詞鎖定狀態全部重設。
|
||||
theNode.resetCandidate()
|
||||
for (i, candidate) in candidates.enumerated() {
|
||||
if candidate.value == value {
|
||||
|
@ -146,13 +176,18 @@ extension Megrez {
|
|||
return node
|
||||
}
|
||||
|
||||
/// 將給定位置的節點的與給定的字串一致的候選字詞的權重複寫為給定權重數值。
|
||||
/// - Parameters:
|
||||
/// - location: 位置。
|
||||
/// - value: 給定字串。
|
||||
/// - overridingScore: 給定權重數值。
|
||||
public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) {
|
||||
for nodeAnchor in nodesCrossingOrEndingAt(location: location) {
|
||||
guard let theNode = nodeAnchor.node else {
|
||||
continue
|
||||
}
|
||||
let candidates = theNode.candidates()
|
||||
// Reset the candidate-fixed state of every node at the location.
|
||||
let candidates = theNode.candidates
|
||||
// 將該位置的所有節點的候選字詞鎖定狀態全部重設。
|
||||
theNode.resetCandidate()
|
||||
for (i, candidate) in candidates.enumerated() {
|
||||
if candidate.value == value {
|
||||
|
@ -164,3 +199,38 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - DumpDOT-related functions.
|
||||
|
||||
extension Megrez.Grid {
|
||||
public var dumpDOT: String {
|
||||
var sst = "digraph {\ngraph [ rankdir=LR ];\nBOS;\n"
|
||||
for (p, span) in mutSpans.enumerated() {
|
||||
for ni in 0...(span.maximumLength) {
|
||||
guard let np: Megrez.Node = span.node(length: ni) else {
|
||||
continue
|
||||
}
|
||||
if p == 0 {
|
||||
sst += "BOS -> \(np.currentKeyValue.value);\n"
|
||||
}
|
||||
|
||||
sst += "\(np.currentKeyValue.value);\n"
|
||||
|
||||
if (p + ni) < mutSpans.count {
|
||||
let dstSpan = mutSpans[p + ni]
|
||||
for q in 0...(dstSpan.maximumLength) {
|
||||
if let dn = dstSpan.node(length: q) {
|
||||
sst += np.currentKeyValue.value + " -> " + dn.currentKeyValue.value + ";\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p + ni) == mutSpans.count {
|
||||
sst += np.currentKeyValue.value + " -> EOS;\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
sst += "EOS;\n}\n"
|
||||
return sst
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,19 +24,52 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
@frozen public struct NodeAnchor {
|
||||
/// 節锚。
|
||||
@frozen public struct NodeAnchor: CustomStringConvertible {
|
||||
/// 節點。一個節锚內不一定有節點。
|
||||
public var node: Node?
|
||||
/// 節锚所在的位置。
|
||||
public var location: Int = 0
|
||||
/// 幅位長度。
|
||||
public var spanningLength: Int = 0
|
||||
/// 累計權重。
|
||||
public var accumulatedScore: Double = 0.0
|
||||
/// 索引鍵的長度。
|
||||
public var keyLength: Int {
|
||||
node?.key().count ?? 0
|
||||
node?.key.count ?? 0
|
||||
}
|
||||
|
||||
/// 將當前節锚列印成一個字串。
|
||||
public var description: String {
|
||||
var stream = ""
|
||||
stream += "{@(" + String(location) + "," + String(spanningLength) + "),"
|
||||
if let node = node {
|
||||
stream += node.description
|
||||
} else {
|
||||
stream += "null"
|
||||
}
|
||||
stream += "}"
|
||||
return stream
|
||||
}
|
||||
|
||||
/// 獲取平衡權重。
|
||||
public var balancedScore: Double {
|
||||
let weightedScore: Double = (Double(spanningLength) - 1) * 2
|
||||
let nodeScore: Double = node?.score() ?? 0
|
||||
let nodeScore: Double = node?.score ?? 0
|
||||
return weightedScore + nodeScore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - DumpDOT-related functions.
|
||||
|
||||
extension Array where Element == Megrez.NodeAnchor {
|
||||
/// 將節锚陣列列印成一個字串。
|
||||
public var description: String {
|
||||
var arrOutputContent = [""]
|
||||
for anchor in self {
|
||||
arrOutputContent.append(anchor.description)
|
||||
}
|
||||
return arrOutputContent.joined(separator: "<-")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,23 +24,28 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
/// 幅位。
|
||||
@frozen public struct Span {
|
||||
private var mutLengthNodeMap: [Int: Megrez.Node]
|
||||
private var mutMaximumLength: Int
|
||||
/// 辭典:以節點長度為索引,以節點為資料值。
|
||||
private var mutLengthNodeMap: [Int: Megrez.Node] = [:]
|
||||
/// 最大節點長度。
|
||||
private var mutMaximumLength: Int = 0
|
||||
|
||||
/// 公開:最長幅距(唯讀)。
|
||||
var maximumLength: Int {
|
||||
mutMaximumLength
|
||||
}
|
||||
|
||||
public init() {
|
||||
mutLengthNodeMap = [:]
|
||||
mutMaximumLength = 0
|
||||
}
|
||||
|
||||
/// 自我清空,各項參數歸零。
|
||||
mutating func clear() {
|
||||
mutLengthNodeMap.removeAll()
|
||||
mutMaximumLength = 0
|
||||
}
|
||||
|
||||
/// 往自身插入一個節點、及給定的節點長度。
|
||||
/// - Parameters:
|
||||
/// - node: 節點。
|
||||
/// - length: 給定的節點長度。
|
||||
mutating func insert(node: Node, length: Int) {
|
||||
mutLengthNodeMap[length] = node
|
||||
if length > mutMaximumLength {
|
||||
|
@ -48,6 +53,9 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
|
||||
/// 移除任何比給定的長度更長的節點。
|
||||
/// - Parameters:
|
||||
/// - length: 給定的節點長度。
|
||||
mutating func removeNodeOfLengthGreaterThan(_ length: Int) {
|
||||
if length > mutMaximumLength { return }
|
||||
var max = 0
|
||||
|
@ -67,6 +75,9 @@ extension Megrez {
|
|||
mutMaximumLength = max
|
||||
}
|
||||
|
||||
/// 給定節點長度,獲取節點。
|
||||
/// - Parameters:
|
||||
/// - length: 給定的節點長度。
|
||||
public func node(length: Int) -> Node? {
|
||||
mutLengthNodeMap[length]
|
||||
}
|
||||
|
|
|
@ -24,55 +24,69 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
public class Node {
|
||||
let mutLM: LanguageModel
|
||||
var mutKey: String
|
||||
var mutScore: Double = 0
|
||||
var mutUnigrams: [Unigram]
|
||||
var mutCandidates: [KeyValuePair]
|
||||
var mutValueUnigramIndexMap: [String: Int]
|
||||
var mutPrecedingBigramMap: [KeyValuePair: [Megrez.Bigram]]
|
||||
|
||||
var mutCandidateFixed: Bool = false
|
||||
var mutSelectedUnigramIndex: Int = 0
|
||||
|
||||
let kSelectedCandidateScore: Double = 99
|
||||
|
||||
public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) {
|
||||
mutLM = LanguageModel()
|
||||
|
||||
mutKey = key
|
||||
mutScore = 0
|
||||
|
||||
mutUnigrams = unigrams
|
||||
mutCandidates = []
|
||||
mutValueUnigramIndexMap = [:]
|
||||
mutPrecedingBigramMap = [:]
|
||||
|
||||
mutCandidateFixed = false
|
||||
mutSelectedUnigramIndex = 0
|
||||
|
||||
if bigrams == [] {
|
||||
node(key: key, unigrams: unigrams, bigrams: bigrams)
|
||||
} else {
|
||||
node(key: key, unigrams: unigrams)
|
||||
}
|
||||
/// 節點。
|
||||
public class Node: CustomStringConvertible {
|
||||
/// 當前節點對應的語言模型。
|
||||
private let mutLM: LanguageModel = .init()
|
||||
/// 鍵。
|
||||
private var mutKey: String = ""
|
||||
/// 當前節點的當前被選中的候選字詞「在該節點內的」目前的權重。
|
||||
private var mutScore: Double = 0
|
||||
/// 單元圖陣列。
|
||||
private var mutUnigrams: [Unigram]
|
||||
/// 雙元圖陣列。
|
||||
private var mutBigrams: [Bigram]
|
||||
/// 候選字詞陣列,以鍵值陣列的形式存在。
|
||||
private var mutCandidates: [KeyValuePair] = []
|
||||
/// 專門「用單元圖資料值來調查索引值」的辭典。
|
||||
private var mutValueUnigramIndexMap: [String: Int] = [:]
|
||||
/// 專門「用給定鍵值來取對應的雙元圖陣列」的辭典。
|
||||
private var mutPrecedingBigramMap: [KeyValuePair: [Megrez.Bigram]] = [:]
|
||||
/// 狀態標記變數,用來記載當前節點是否處於候選字詞鎖定狀態。
|
||||
private var mutCandidateFixed: Bool = false
|
||||
/// 用來登記「當前選中的單元圖」的索引值的變數。
|
||||
private var mutSelectedUnigramIndex: Int = 0
|
||||
/// 用來登記要施加給「『被標記為選中狀態』的候選字詞」的複寫權重的數值。
|
||||
private let kSelectedCandidateScore: Double = 99
|
||||
/// 將當前節點列印成一個字串。
|
||||
public var description: String {
|
||||
"(node,key:\(mutKey),fixed:\(mutCandidateFixed ? "true" : "false"),selected:\(mutSelectedUnigramIndex),\(mutUnigrams))"
|
||||
}
|
||||
|
||||
public func node(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) {
|
||||
var unigrams = unigrams
|
||||
/// 公開:候選字詞陣列(唯讀),以鍵值陣列的形式存在。
|
||||
var candidates: [KeyValuePair] { mutCandidates }
|
||||
/// 公開:用來登記「當前選中的單元圖」的索引值的變數(唯讀)。
|
||||
var isCandidateFixed: Bool { mutCandidateFixed }
|
||||
|
||||
/// 公開:鍵(唯讀)。
|
||||
var key: String { mutKey }
|
||||
/// 公開:當前節點的當前被選中的候選字詞「在該節點內的」目前的權重(唯讀)。
|
||||
var score: Double { mutScore }
|
||||
/// 公開:當前被選中的候選字詞的鍵值配對。
|
||||
var currentKeyValue: KeyValuePair {
|
||||
mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePair() : mutCandidates[mutSelectedUnigramIndex]
|
||||
}
|
||||
|
||||
/// 公開:給出當前單元圖陣列內最高的權重數值。
|
||||
var highestUnigramScore: Double { mutUnigrams.isEmpty ? 0.0 : mutUnigrams[0].score }
|
||||
|
||||
/// 初期化一個節點。
|
||||
/// - Parameters:
|
||||
/// - key: 索引鍵。
|
||||
/// - unigrams: 單元圖陣列。
|
||||
/// - bigrams: 雙元圖陣列(非必填)。
|
||||
public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) {
|
||||
mutKey = key
|
||||
unigrams.sort {
|
||||
mutUnigrams = unigrams
|
||||
mutBigrams = bigrams
|
||||
|
||||
mutUnigrams.sort {
|
||||
$0.score > $1.score
|
||||
}
|
||||
|
||||
if !mutUnigrams.isEmpty {
|
||||
mutScore = mutUnigrams[0].score
|
||||
}
|
||||
|
||||
for (i, theGram) in unigrams.enumerated() {
|
||||
mutValueUnigramIndexMap[theGram.keyValue.value] = i
|
||||
mutCandidates.append(theGram.keyValue)
|
||||
for (i, gram) in mutUnigrams.enumerated() {
|
||||
mutValueUnigramIndexMap[gram.keyValue.value] = i
|
||||
mutCandidates.append(gram.keyValue)
|
||||
}
|
||||
|
||||
for gram in bigrams {
|
||||
|
@ -80,11 +94,14 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
|
||||
/// 對擁有「給定的前述鍵值陣列」的節點提權。
|
||||
/// - Parameters:
|
||||
/// - precedingKeyValues: 前述鍵值陣列。
|
||||
public func primeNodeWith(precedingKeyValues: [KeyValuePair]) {
|
||||
var newIndex = mutSelectedUnigramIndex
|
||||
var max = mutScore
|
||||
|
||||
if !isCandidateFixed() {
|
||||
if !isCandidateFixed {
|
||||
for neta in precedingKeyValues {
|
||||
let bigrams = mutPrecedingBigramMap[neta] ?? []
|
||||
for bigram in bigrams {
|
||||
|
@ -107,16 +124,17 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
|
||||
public func isCandidateFixed() -> Bool { mutCandidateFixed }
|
||||
|
||||
public func candidates() -> [KeyValuePair] { mutCandidates }
|
||||
|
||||
/// 選中位於給定索引位置的候選字詞。
|
||||
/// - Parameters:
|
||||
/// - index: 索引位置。
|
||||
/// - fix: 是否將當前解點標記為「候選詞已鎖定」的狀態。
|
||||
public func selectCandidateAt(index: Int = 0, fix: Bool = false) {
|
||||
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index
|
||||
mutCandidateFixed = fix
|
||||
mutScore = kSelectedCandidateScore
|
||||
}
|
||||
|
||||
/// 重設該節點的候選字詞狀態。
|
||||
public func resetCandidate() {
|
||||
mutSelectedUnigramIndex = 0
|
||||
mutCandidateFixed = false
|
||||
|
@ -125,16 +143,19 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
|
||||
/// 選中位於給定索引位置的候選字詞、且施加給定的權重。
|
||||
/// - Parameters:
|
||||
/// - index: 索引位置。
|
||||
/// - score: 給定權重條件。
|
||||
public func selectFloatingCandidateAt(index: Int, score: Double) {
|
||||
mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index
|
||||
mutCandidateFixed = false
|
||||
mutScore = score
|
||||
}
|
||||
|
||||
public func key() -> String { mutKey }
|
||||
|
||||
public func score() -> Double { mutScore }
|
||||
|
||||
/// 藉由給定的候選字詞字串,找出在庫的單元圖權重數值。沒有的話就找零。
|
||||
/// - Parameters:
|
||||
/// - candidate: 給定的候選字詞字串。
|
||||
public func scoreFor(candidate: String) -> Double {
|
||||
for unigram in mutUnigrams {
|
||||
if unigram.keyValue.value == candidate {
|
||||
|
@ -144,14 +165,6 @@ extension Megrez {
|
|||
return 0.0
|
||||
}
|
||||
|
||||
public func currentKeyValue() -> KeyValuePair {
|
||||
mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePair() : mutCandidates[mutSelectedUnigramIndex]
|
||||
}
|
||||
|
||||
public func highestUnigramScore() -> Double {
|
||||
mutUnigrams.isEmpty ? 0.0 : mutUnigrams[0].score
|
||||
}
|
||||
|
||||
public static func == (lhs: Node, rhs: Node) -> Bool {
|
||||
lhs.mutUnigrams == rhs.mutUnigrams && lhs.mutCandidates == rhs.mutCandidates
|
||||
&& lhs.mutValueUnigramIndexMap == rhs.mutValueUnigramIndexMap
|
||||
|
|
|
@ -24,19 +24,23 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
// 這裡充其量只是框架,回頭實際使用時需要派生一個型別、且重寫相關函數。
|
||||
// 這裡寫了一點假內容,不然有些 Swift 格式化工具會破壞掉函數的參數設計。
|
||||
/// 語言模型框架,回頭實際使用時需要派生一個型別、且重寫相關函數。
|
||||
open class LanguageModel {
|
||||
public init() {}
|
||||
|
||||
// 這裡寫了一點假內容,不然有些 Swift 格式化工具會破壞掉函數的參數設計。
|
||||
|
||||
/// 給定鍵,讓語言模型找給一筆單元圖。
|
||||
open func unigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
key.isEmpty ? [Megrez.Unigram]() : [Megrez.Unigram]()
|
||||
}
|
||||
|
||||
/// 給定當前鍵與前述鍵,讓語言模型找給一筆雙元圖。
|
||||
open func bigramsForKeys(precedingKey: String, key: String) -> [Megrez.Bigram] {
|
||||
precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]()
|
||||
}
|
||||
|
||||
/// 給定鍵,
|
||||
open func hasUnigramsFor(key: String) -> Bool {
|
||||
key.count != 0
|
||||
}
|
||||
|
|
|
@ -24,17 +24,28 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
@frozen public struct Bigram: Equatable {
|
||||
/// 雙元圖。
|
||||
@frozen public struct Bigram: Equatable, CustomStringConvertible {
|
||||
/// 當前鍵值。
|
||||
public var keyValue: KeyValuePair
|
||||
/// 前述鍵值。
|
||||
public var precedingKeyValue: KeyValuePair
|
||||
/// 權重。
|
||||
public var score: Double
|
||||
// var paired: String
|
||||
/// 將當前雙元圖列印成一個字串。
|
||||
public var description: String {
|
||||
"(" + keyValue.description + "|" + precedingKeyValue.description + "," + String(score) + ")"
|
||||
}
|
||||
|
||||
/// 初期化一筆「雙元圖」。一筆雙元圖由一組前述鍵值配對、一組當前鍵值配對、與一筆權重數值組成。
|
||||
/// - Parameters:
|
||||
/// - precedingKeyValue: 前述鍵值。
|
||||
/// - keyValue: 當前鍵值。
|
||||
/// - score: 權重(雙精度小數)。
|
||||
public init(precedingKeyValue: KeyValuePair, keyValue: KeyValuePair, score: Double) {
|
||||
self.keyValue = keyValue
|
||||
self.precedingKeyValue = precedingKeyValue
|
||||
self.score = score
|
||||
// paired = "(" + keyValue.paired + "|" + precedingKeyValue.paired + "," + String(score) + ")"
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
|
@ -44,16 +55,6 @@ extension Megrez {
|
|||
// hasher.combine(paired)
|
||||
}
|
||||
|
||||
// static func getPairedBigrams(grams: [Bigram]) -> String {
|
||||
// var arrOutputContent = [""]
|
||||
// var index = 0
|
||||
// for gram in grams {
|
||||
// arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.paired])
|
||||
// index += 1
|
||||
// }
|
||||
// return "[" + String(grams.count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
|
||||
// }
|
||||
|
||||
public static func == (lhs: Bigram, rhs: Bigram) -> Bool {
|
||||
lhs.precedingKeyValue == rhs.precedingKeyValue && lhs.keyValue == rhs.keyValue && lhs.score == rhs.score
|
||||
}
|
||||
|
@ -62,13 +63,18 @@ extension Megrez {
|
|||
lhs.precedingKeyValue < rhs.precedingKeyValue
|
||||
|| (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue))
|
||||
}
|
||||
|
||||
var description: String {
|
||||
"\(keyValue):\(score)"
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"Bigram(keyValue: \(keyValue), score: \(score))"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - DumpDOT-related functions.
|
||||
|
||||
extension Array where Element == Megrez.Bigram {
|
||||
/// 將雙元圖陣列列印成一個字串。
|
||||
public var description: String {
|
||||
var arrOutputContent = [""]
|
||||
for (index, gram) in enumerated() {
|
||||
arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description])
|
||||
}
|
||||
return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,21 +24,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
@frozen public struct Unigram: Equatable {
|
||||
/// 單元圖。
|
||||
@frozen public struct Unigram: Equatable, CustomStringConvertible {
|
||||
/// 鍵值。
|
||||
public var keyValue: KeyValuePair
|
||||
/// 權重。
|
||||
public var score: Double
|
||||
// var paired: String
|
||||
/// 將當前單元圖列印成一個字串。
|
||||
public var description: String {
|
||||
"(" + keyValue.description + "," + String(score) + ")"
|
||||
}
|
||||
|
||||
/// 初期化一筆「單元圖」。一筆單元圖由一組鍵值配對與一筆權重數值組成。
|
||||
/// - Parameters:
|
||||
/// - keyValue: 鍵值。
|
||||
/// - score: 權重(雙精度小數)。
|
||||
public init(keyValue: KeyValuePair, score: Double) {
|
||||
self.keyValue = keyValue
|
||||
self.score = score
|
||||
// paired = "(" + keyValue.paired + "," + String(score) + ")"
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(keyValue)
|
||||
hasher.combine(score)
|
||||
// hasher.combine(paired)
|
||||
}
|
||||
|
||||
// 這個函數不再需要了。
|
||||
|
@ -46,16 +54,6 @@ extension Megrez {
|
|||
a.score > b.score
|
||||
}
|
||||
|
||||
// static func getPairedUnigrams(grams: [Unigram]) -> String {
|
||||
// var arrOutputContent = [""]
|
||||
// var index = 0
|
||||
// for gram in grams {
|
||||
// arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.paired])
|
||||
// index += 1
|
||||
// }
|
||||
// return "[" + String(grams.count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
|
||||
// }
|
||||
|
||||
public static func == (lhs: Unigram, rhs: Unigram) -> Bool {
|
||||
lhs.keyValue == rhs.keyValue && lhs.score == rhs.score
|
||||
}
|
||||
|
@ -63,13 +61,18 @@ extension Megrez {
|
|||
public static func < (lhs: Unigram, rhs: Unigram) -> Bool {
|
||||
lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
"\(keyValue):\(score)"
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"Unigram(keyValue: \(keyValue), score: \(score))"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - DumpDOT-related functions.
|
||||
|
||||
extension Array where Element == Megrez.Unigram {
|
||||
/// 將單元圖陣列列印成一個字串。
|
||||
public var description: String {
|
||||
var arrOutputContent = [""]
|
||||
for (index, gram) in enumerated() {
|
||||
arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.description])
|
||||
}
|
||||
return "[" + String(count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,21 +24,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
*/
|
||||
|
||||
extension Megrez {
|
||||
@frozen public struct KeyValuePair: Equatable, Hashable, Comparable {
|
||||
/// 鍵值配對。
|
||||
@frozen public struct KeyValuePair: Equatable, Hashable, Comparable, CustomStringConvertible {
|
||||
/// 鍵。一般情況下用來放置讀音等可以用來作為索引的內容。
|
||||
public var key: String
|
||||
/// 資料值。
|
||||
public var value: String
|
||||
// public var paired: String
|
||||
/// 將當前鍵值列印成一個字串。
|
||||
public var description: String {
|
||||
"(" + key + "," + value + ")"
|
||||
}
|
||||
|
||||
/// 初期化一組鍵值配對
|
||||
/// - Parameters:
|
||||
/// - key: 鍵。一般情況下用來放置讀音等可以用來作為索引的內容。
|
||||
/// - value: 資料值。
|
||||
public init(key: String = "", value: String = "") {
|
||||
self.key = key
|
||||
self.value = value
|
||||
// paired = "(" + key + "," + value + ")"
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(key)
|
||||
hasher.combine(value)
|
||||
// hasher.combine(paired)
|
||||
}
|
||||
|
||||
public static func == (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool {
|
||||
|
@ -60,13 +68,5 @@ extension Megrez {
|
|||
public static func >= (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool {
|
||||
(lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
"(\(key), \(value))"
|
||||
}
|
||||
|
||||
public var debugDescription: String {
|
||||
"KeyValuePair(key: \(key), value: \(value))"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
"Debug Mode" = "Debug Mode";
|
||||
"Dictionary" = "Dictionary";
|
||||
"Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode";
|
||||
"Enable CNS11643 Support (2022-01-27)" = "Enable CNS11643 Support (2022-01-27)";
|
||||
"Enable CNS11643 Support (2022-04-27)" = "Enable CNS11643 Support (2022-04-27)";
|
||||
"Enable Space key for calling candidate window" = "Enable Space key for calling candidate window";
|
||||
"Enable symbol input support (incl. certain emoji symbols)" = "Enable symbol input support (incl. certain emoji symbols)";
|
||||
"English" = "English";
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
"Debug Mode" = "Debug Mode";
|
||||
"Dictionary" = "Dictionary";
|
||||
"Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode";
|
||||
"Enable CNS11643 Support (2022-01-27)" = "Enable CNS11643 Support (2022-01-27)";
|
||||
"Enable CNS11643 Support (2022-04-27)" = "Enable CNS11643 Support (2022-04-27)";
|
||||
"Enable Space key for calling candidate window" = "Enable Space key for calling candidate window";
|
||||
"Enable symbol input support (incl. certain emoji symbols)" = "Enable symbol input support (incl. certain emoji symbols)";
|
||||
"English" = "English";
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
"Debug Mode" = "欠陥辿着モード";
|
||||
"Dictionary" = "辞書設定";
|
||||
"Emulating select-candidate-per-character mode" = "漢字1つづつ全候補選択入力モード";
|
||||
"Enable CNS11643 Support (2022-01-27)" = "全字庫モード // 入力可能な漢字数を倍増す (2022-01-07)";
|
||||
"Enable CNS11643 Support (2022-04-27)" = "全字庫モード // 入力可能な漢字数を倍増す (2022-04-27)";
|
||||
"Enable Space key for calling candidate window" = "Space キーで入力候補を呼び出す";
|
||||
"Enable symbol input support (incl. certain emoji symbols)" = "僅かなる絵文字も含む符号入力サポートを起用";
|
||||
"English" = "英語";
|
||||
|
|
|
@ -105,11 +105,11 @@
|
|||
"Choose your preferred layout of the candidate window." = "选择您所偏好的候选字窗布局。";
|
||||
"Cursor Selection:" = "选字游标:";
|
||||
"Dachen (Microsoft Standard / Wang / 01, etc.)" = "大千排列 (微软标准/王安/零壹/仲鼎/国乔)";
|
||||
"Dachen 26 (libChewing)" = "酷音大千二十六键";
|
||||
"Dachen 26 (libChewing)" = "酷音大千二十六键排列";
|
||||
"Debug Mode" = "侦错模式";
|
||||
"Dictionary" = "辞典";
|
||||
"Emulating select-candidate-per-character mode" = "模拟 90 年代前期注音逐字选字输入风格";
|
||||
"Enable CNS11643 Support (2022-01-27)" = "启用 CNS11643 全字库支援 (2022-01-07)";
|
||||
"Enable CNS11643 Support (2022-04-27)" = "启用 CNS11643 全字库支援 (2022-04-27)";
|
||||
"Enable Space key for calling candidate window" = "敲空格键以呼出候选字窗";
|
||||
"Enable symbol input support (incl. certain emoji symbols)" = "启用包括少许绘文字在内的符号输入支援";
|
||||
"English" = "英语";
|
||||
|
|
|
@ -105,11 +105,11 @@
|
|||
"Choose your preferred layout of the candidate window." = "選擇您所偏好的候選字窗佈局。";
|
||||
"Cursor Selection:" = "選字游標:";
|
||||
"Dachen (Microsoft Standard / Wang / 01, etc.)" = "大千排列 (微軟標準/王安/零壹/仲鼎/國喬)";
|
||||
"Dachen 26 (libChewing)" = "酷音大千二十六鍵";
|
||||
"Dachen 26 (libChewing)" = "酷音大千二十六鍵排列";
|
||||
"Debug Mode" = "偵錯模式";
|
||||
"Dictionary" = "辭典";
|
||||
"Emulating select-candidate-per-character mode" = "模擬 90 年代前期注音逐字選字輸入風格";
|
||||
"Enable CNS11643 Support (2022-01-27)" = "啟用 CNS11643 全字庫支援 (2022-01-07)";
|
||||
"Enable CNS11643 Support (2022-04-27)" = "啟用 CNS11643 全字庫支援 (2022-04-27)";
|
||||
"Enable Space key for calling candidate window" = "敲空格鍵以呼出候選字窗";
|
||||
"Enable symbol input support (incl. certain emoji symbols)" = "啟用包括少許繪文字在內的符號輸入支援";
|
||||
"English" = "英語";
|
||||
|
|
|
@ -113,7 +113,7 @@ struct suiPrefPaneDictionary: View {
|
|||
mgrPrefs.shouldAutoReloadUserDataFiles = value
|
||||
}
|
||||
Divider()
|
||||
Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-01-27)"), isOn: $selEnableCNS11643)
|
||||
Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-04-27)"), isOn: $selEnableCNS11643)
|
||||
.onChange(of: selEnableCNS11643) { value in
|
||||
mgrPrefs.cns11643Enabled = value
|
||||
mgrLangModel.setCNSEnabled(value)
|
||||
|
|
|
@ -682,7 +682,7 @@
|
|||
<constraints>
|
||||
<constraint firstAttribute="height" constant="16" id="KdE-Vd-Y50"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="check" title="Enable CNS11643 Support (2022-01-07)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="W24-T4-cg0">
|
||||
<buttonCell key="cell" type="check" title="Enable CNS11643 Support (2022-04-27)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="W24-T4-cg0">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
</buttonCell>
|
||||
|
|
|
@ -152,8 +152,8 @@
|
|||
/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */
|
||||
"Uyz-xL-TVN.title" = "Output Settings";
|
||||
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "Enable CNS11643 Support (2022-01-07)";
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "Enable CNS11643 Support (2022-04-27)";
|
||||
|
||||
/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */
|
||||
"Wvt-HE-LOv.title" = "Keyboard Layout";
|
||||
|
|
|
@ -152,8 +152,8 @@
|
|||
/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */
|
||||
"Uyz-xL-TVN.title" = "出力設定";
|
||||
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-01-07)";
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-04-27)";
|
||||
|
||||
/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */
|
||||
"Wvt-HE-LOv.title" = "キーボード";
|
||||
|
|
|
@ -152,8 +152,8 @@
|
|||
/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */
|
||||
"Uyz-xL-TVN.title" = "输出设定";
|
||||
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-01-07)";
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-04-27)";
|
||||
|
||||
/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */
|
||||
"Wvt-HE-LOv.title" = "键盘布局";
|
||||
|
|
|
@ -152,8 +152,8 @@
|
|||
/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */
|
||||
"Uyz-xL-TVN.title" = "輸出設定";
|
||||
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-01-07)";
|
||||
/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-04-27)"; ObjectID = "W24-T4-cg0"; */
|
||||
"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-04-27)";
|
||||
|
||||
/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */
|
||||
"Wvt-HE-LOv.title" = "鍵盤佈局";
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.9</string>
|
||||
<string>1.6.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1959</string>
|
||||
<string>1960</string>
|
||||
<key>UpdateInfoEndpoint</key>
|
||||
<string>https://gitee.com/vchewing/vChewing-macOS/raw/main/Update-Info.plist</string>
|
||||
<key>UpdateInfoSite</key>
|
||||
|
|
|
@ -726,7 +726,7 @@
|
|||
<key>USE_HFS+_COMPRESSION</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<string>1.5.9</string>
|
||||
<string>1.6.0</string>
|
||||
</dict>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */; };
|
||||
5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */; };
|
||||
5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */; };
|
||||
5B38F5A0281E2E49007D5F5D /* 1_Walker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */; };
|
||||
5B38F5A1281E2E49007D5F5D /* 1_BlockReadingBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1515FC0EB100ABF4B3 /* 1_BlockReadingBuilder.swift */; };
|
||||
5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */; };
|
||||
5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */; };
|
||||
|
@ -107,7 +106,7 @@
|
|||
6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; };
|
||||
6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; };
|
||||
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; };
|
||||
D456576E279E4F7B00DF6BC9 /* InputHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* InputHandler.swift */; };
|
||||
D456576E279E4F7B00DF6BC9 /* InputSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* InputSignal.swift */; };
|
||||
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
|
||||
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
|
||||
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */; };
|
||||
|
@ -300,7 +299,6 @@
|
|||
6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_NodeAnchor.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_Span.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Unigram.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 1_Walker.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
|
||||
|
@ -313,7 +311,7 @@
|
|||
6ACA41F315FC1D9000935EF6 /* Installer-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Installer-Prefix.pch"; path = "Installer/Installer-Prefix.pch"; sourceTree = SOURCE_ROOT; };
|
||||
D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "vChewing-Bridging-Header.h"; sourceTree = "<group>"; tabWidth = 4; 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; };
|
||||
D456576D279E4F7B00DF6BC9 /* InputHandler.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputHandler.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
D456576D279E4F7B00DF6BC9 /* InputSignal.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputSignal.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; };
|
||||
D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
|
@ -413,7 +411,7 @@
|
|||
5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */,
|
||||
D4E569DA27A34CC100AC2CEF /* CTools.h */,
|
||||
D4E569DB27A34CC100AC2CEF /* CTools.m */,
|
||||
D456576D279E4F7B00DF6BC9 /* InputHandler.swift */,
|
||||
D456576D279E4F7B00DF6BC9 /* InputSignal.swift */,
|
||||
D461B791279DAC010070E734 /* InputState.swift */,
|
||||
5BD0113C2818543900609769 /* KeyHandler_Core.swift */,
|
||||
5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */,
|
||||
|
@ -769,7 +767,6 @@
|
|||
children = (
|
||||
6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */,
|
||||
6A0D4F1515FC0EB100ABF4B3 /* 1_BlockReadingBuilder.swift */,
|
||||
6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */,
|
||||
6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */,
|
||||
6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */,
|
||||
6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */,
|
||||
|
@ -1054,7 +1051,7 @@
|
|||
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */,
|
||||
5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */,
|
||||
5B949BD92816DC5400D87B5D /* LineReader.swift in Sources */,
|
||||
D456576E279E4F7B00DF6BC9 /* InputHandler.swift in Sources */,
|
||||
D456576E279E4F7B00DF6BC9 /* InputSignal.swift in Sources */,
|
||||
5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */,
|
||||
5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */,
|
||||
5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */,
|
||||
|
@ -1086,7 +1083,6 @@
|
|||
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
|
||||
5B61B0CA280BEFD4002E3CFA /* KeyHandler_Misc.swift in Sources */,
|
||||
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */,
|
||||
5B38F5A0281E2E49007D5F5D /* 1_Walker.swift in Sources */,
|
||||
5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */,
|
||||
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
|
||||
5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */,
|
||||
|
@ -1296,7 +1292,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1959;
|
||||
CURRENT_PROJECT_VERSION = 1960;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
|
@ -1319,7 +1315,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11.5;
|
||||
MARKETING_VERSION = 1.5.9;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
|
||||
|
@ -1352,7 +1348,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1959;
|
||||
CURRENT_PROJECT_VERSION = 1960;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
@ -1371,7 +1367,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11.5;
|
||||
MARKETING_VERSION = 1.5.9;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
|
||||
|
@ -1486,7 +1482,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1959;
|
||||
CURRENT_PROJECT_VERSION = 1960;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
|
@ -1521,7 +1517,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11.5;
|
||||
MARKETING_VERSION = 1.5.9;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -1553,7 +1549,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1959;
|
||||
CURRENT_PROJECT_VERSION = 1960;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
|
@ -1583,7 +1579,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11.5;
|
||||
MARKETING_VERSION = 1.5.9;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -1666,7 +1662,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1959;
|
||||
CURRENT_PROJECT_VERSION = 1960;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
|
@ -1691,7 +1687,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11.5;
|
||||
MARKETING_VERSION = 1.5.9;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -1718,7 +1714,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1959;
|
||||
CURRENT_PROJECT_VERSION = 1960;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
|
@ -1738,7 +1734,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11.5;
|
||||
MARKETING_VERSION = 1.5.9;
|
||||
MARKETING_VERSION = 1.6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
Loading…
Reference in New Issue