Compare commits

..

No commits in common. "main" and "3.7.1" have entirely different histories.
main ... 3.7.1

271 changed files with 17295 additions and 21458 deletions

View File

@ -1,4 +1,4 @@
name: debug-macOS-MainAssembly
name: Build-with-macOS-latest
on:
push:
branches: [ "main" ]
@ -7,16 +7,16 @@ on:
jobs:
build:
name: Build
runs-on: macOS-13
name: Build (latest)
runs-on: macOS-latest
env:
GIT_SSL_NO_VERIFY: true
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '^15.1'
xcode-version: '^15.0'
- uses: actions/checkout@v1
- name: Clean
run: make spmClean
run: make clean
- name: Build
run: make spmDebug
run: git pull --all && git submodule sync; make update; make

View File

@ -194,9 +194,9 @@ func prepareDatabase() -> Bool {
PRIMARY KEY (theChar)
) WITHOUT ROWID;
"""
guard sqlite3_open(":memory:", &ptrSQL) == SQLITE_OK else { return false }
guard sqlite3_open(urlSQLite, &ptrSQL) == SQLITE_OK else { return false }
guard sqlite3_exec(ptrSQL, "PRAGMA synchronous = OFF;", nil, nil, nil) == SQLITE_OK else { return false }
guard sqlite3_exec(ptrSQL, "PRAGMA journal_mode = OFF;", nil, nil, nil) == SQLITE_OK else { return false }
guard sqlite3_exec(ptrSQL, "PRAGMA journal_mode = MEMORY;", nil, nil, nil) == SQLITE_OK else { return false }
guard sqlMakeTableMACV.runAsSQLExec(dbPointer: &ptrSQL) else { return false }
guard "begin;".runAsSQLExec(dbPointer: &ptrSQL) else { return false }
@ -231,20 +231,6 @@ func prepareDatabase() -> Bool {
return true
}
// MARK: - Dump SQLite3 Memory Database to File.
@discardableResult func dumpSQLDB() -> Bool {
var ptrSQLTarget: OpaquePointer?
defer { sqlite3_close_v2(ptrSQLTarget) }
guard sqlite3_open(urlSQLite, &ptrSQLTarget) == SQLITE_OK else { return false }
let ptrBackupObj = sqlite3_backup_init(ptrSQLTarget, "main", ptrSQL, "main")
if ptrBackupObj != nil {
sqlite3_backup_step(ptrBackupObj, -1)
sqlite3_backup_finish(ptrBackupObj)
}
return sqlite3_errcode(ptrSQLTarget) == SQLITE_OK
}
// MARK: -
func rawDictForPhrases(isCHS: Bool) -> [Unigram] {
@ -1058,19 +1044,6 @@ func healthCheck(_ data: [Unigram]) -> String {
return result
}
// MARK: - Flags
struct TaskFlags: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let common = TaskFlags(rawValue: 1 << 0)
public static let chs = TaskFlags(rawValue: 1 << 1)
public static let cht = TaskFlags(rawValue: 1 << 2)
}
// MARK: -
var compileJSON = false
@ -1088,76 +1061,62 @@ func main() {
compileJSON = false
compileSQLite = true
}
let prepared = prepareDatabase()
if compileSQLite, !prepared {
if !prepareDatabase(), compileSQLite {
NSLog("// SQLite 資料庫初期化失敗。")
exit(-1)
}
var taskFlags: TaskFlags = [.common, .chs, .cht] {
didSet {
guard taskFlags.isEmpty else { return }
NSLog("// 全部 TXT 辭典檔案建置完畢。")
if compileJSON {
NSLog("// 全部 JSON 辭典檔案建置完畢。")
}
if compileSQLite, prepared {
NSLog("// 開始整合反查資料。")
mapReverseLookupForCheck.forEach { key, values in
values.reversed().forEach { valueLiteral in
let value = cnvPhonabetToASCII(valueLiteral)
if !rangeMapReverseLookup[key, default: []].contains(value) {
rangeMapReverseLookup[key, default: []].insert(value, at: 0)
}
}
}
NSLog("// 反查資料整合完畢。")
NSLog("// 準備建置 SQL 資料庫。")
writeMainMapToSQL(rangeMapJSONCHS, column: "theDataCHS")
writeMainMapToSQL(rangeMapJSONCHT, column: "theDataCHT")
writeMainMapToSQL(rangeMapSymbols, column: "theDataSYMB")
writeMainMapToSQL(rangeMapZhuyinwen, column: "theDataCHEW")
writeMainMapToSQL(rangeMapCNS, column: "theDataCNS")
writeRevLookupMapToSQL(rangeMapReverseLookup)
let committed = "commit;".runAsSQLExec(dbPointer: &ptrSQL)
assert(committed)
let compressed = "VACUUM;".runAsSQLExec(dbPointer: &ptrSQL)
assert(compressed)
if !dumpSQLDB() {
NSLog("// SQLite 辭典傾印失敗。")
} else {
NSLog("// 全部 SQLite 辭典檔案建置完畢。")
}
sqlite3_close_v2(ptrSQL)
}
}
}
let globalQueue = DispatchQueue.global(qos: .default)
let group = DispatchGroup()
group.enter()
globalQueue.async {
NSLog("// 準備編譯符號表情ㄅ文語料檔案。")
commonFileOutput()
taskFlags.remove(.common)
group.leave()
}
group.enter()
globalQueue.async {
NSLog("// 準備編譯繁體中文核心語料檔案。")
fileOutput(isCHS: false)
taskFlags.remove(.cht)
group.leave()
}
group.enter()
globalQueue.async {
NSLog("// 準備編譯簡體中文核心語料檔案。")
fileOutput(isCHS: true)
taskFlags.remove(.chs)
group.leave()
}
//
group.wait()
NSLog("// 全部 TXT 辭典檔案建置完畢。")
if compileJSON {
NSLog("// 全部 JSON 辭典檔案建置完畢。")
}
if compileSQLite {
NSLog("// 開始整合反查資料。")
mapReverseLookupForCheck.forEach { key, values in
values.reversed().forEach { valueLiteral in
let value = cnvPhonabetToASCII(valueLiteral)
if !rangeMapReverseLookup[key, default: []].contains(value) {
rangeMapReverseLookup[key, default: []].insert(value, at: 0)
}
}
}
NSLog("// 反查資料整合完畢。")
NSLog("// 準備建置 SQL 資料庫。")
writeMainMapToSQL(rangeMapJSONCHS, column: "theDataCHS")
writeMainMapToSQL(rangeMapJSONCHT, column: "theDataCHT")
writeMainMapToSQL(rangeMapSymbols, column: "theDataSYMB")
writeMainMapToSQL(rangeMapZhuyinwen, column: "theDataCHEW")
writeMainMapToSQL(rangeMapCNS, column: "theDataCNS")
writeRevLookupMapToSQL(rangeMapReverseLookup)
let committed = "commit;".runAsSQLExec(dbPointer: &ptrSQL)
assert(committed)
let compressed = "VACUUM;".runAsSQLExec(dbPointer: &ptrSQL)
assert(compressed)
sqlite3_close_v2(ptrSQL)
NSLog("// 全部 SQLite 辭典檔案建置完畢。")
}
}
main()

View File

@ -50,7 +50,7 @@ var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
else {
return []
}
return TISInputSource.match(modeIDs: tsInputModeListKey.keys.map(\.description))
return tsInputModeListKey.keys.compactMap { TISInputSource.generate(from: $0) }
}
// MARK: - NSApp Activation Helper

View File

@ -10,8 +10,6 @@ import AppKit
import SwiftUI
public struct MainView: View {
static let strCopyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL"
@State var pendingSheetPresenting = false
@State var isShowingAlertForFailedInstallation = false
@State var isShowingAlertForMissingPostInstall = false
@ -55,7 +53,6 @@ public struct MainView: View {
Text("v\(versionString) Build \(installingVersion)").lineLimit(1)
}.fixedSize()
Text("i18n:installer.APP_DERIVED_FROM").font(.custom("Tahoma", size: 11))
Text(Self.strCopyrightLabel).font(.custom("Tahoma", size: 11))
Text("i18n:installer.DEV_CREW").font(.custom("Tahoma", size: 11)).padding([.vertical], 2)
}
}

View File

@ -1,30 +1,30 @@
"Abort" = "Abort";
"Attention" = "Attention";
"vChewing Input Method" = "vChewing Input Method";
"i18n:installer.DO_APP_UPGRADE" = "Accept & Upgrade";
"Cancel" = "Cancel";
"Cannot activate the input method." = "Cannot activate the input method.";
"Cannot copy the file to the destination." = "Cannot copy the file to the destination.";
"Cannot find input source %@ after registration." = "Cannot find input source %@ after registration.";
"Cannot register input source %@ at %@." = "Cannot register input source %@ at %@.";
"Continue" = "Continue";
"Fatal Error" = "Fatal Error";
"i18n:installer.ACCEPT_INSTALLATION" = "I Accept";
"i18n:installer.APP_DERIVED_FROM" = "Was derived from OpenVanilla McBopopmofo Project (MIT-License).";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "Cancel";
"i18n:installer.DEV_CREW" = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License).";
"i18n:installer.DISCLAIMER_TEXT" = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database.";
"i18n:installer.DO_APP_UPGRADE" = "Accept & Upgrade";
"i18n:installer.EULA_PROMPT_NOTICE" = "By installing the software, you must accept the terms above.";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "vChewing Installer";
"i18n:installer.INSTALLER_APP_TITLE" = "vChewing Installer";
"i18n:installer.LICENSE_TITLE" = "MIT-NTL License:";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "Stopping the old version. This may take up to one minute…";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.";
"Install Failed" = "Install Failed";
"Installation Successful" = "Installation Successful";
"OK" = "OK";
"Stopping the old version. This may take up to one minute…" = "Stopping the old version. This may take up to one minute…";
"vChewing Input Method" = "vChewing Input Method";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account.";
"Stopping the old version. This may take up to one minute…" = "Stopping the old version. This may take up to one minute…";
"Attention" = "Attention";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing is upgraded, but please log out or reboot for the new version to be fully functional.";
"Fatal Error" = "Fatal Error";
"Abort" = "Abort";
"Cannot register input source %@ at %@." = "Cannot register input source %@ at %@.";
"Cannot find input source %@ after registration." = "Cannot find input source %@ after registration.";
"Warning" = "Warning";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.";
"Continue" = "Continue";
"i18n:installer.DISCLAIMER_TEXT" = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database.";
"i18n:installer.INSTALLER_APP_TITLE" = "vChewing Installer";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "vChewing Installer";
"i18n:installer.ACCEPT_INSTALLATION" = "I Accept";
"i18n:installer.CANCEL_INSTALLATION" = "Cancel";
"i18n:installer.LICENSE_TITLE" = "MIT-NTL License:";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.APP_DERIVED_FROM" = "Was derived from OpenVanilla McBopopmofo Project (MIT-License).";
"i18n:installer.DEV_CREW" = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License).";
"i18n:installer.EULA_PROMPT_NOTICE" = "By installing the software, you must accept the terms above.";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "Stopping the old version. This may take up to one minute…";

View File

@ -1,30 +1,30 @@
"Abort" = "中止";
"Attention" = "ご注意";
"vChewing Input Method" = "威注音入力アプリ";
"i18n:installer.DO_APP_UPGRADE" = "承認と更新";
"Cancel" = "取消";
"Cannot activate the input method." = "入力アプリ、起動失敗。";
"Cannot copy the file to the destination." = "目標へファイルのコピーできません。";
"Cannot find input source %@ after registration." = "登録済みですが「%@」は見つけませんでした。";
"Cannot register input source %@ at %@." = "「%2$@」で入力アプリ「\"%1$@\"」の実装は失敗しました。";
"Continue" = "続行";
"Fatal Error" = "致命錯乱";
"i18n:installer.ACCEPT_INSTALLATION" = "承認する";
"i18n:installer.APP_DERIVED_FROM" = "曾て OpenVanilla 小麦注音プロジェクト (MIT-License) から派生。";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "取消";
"i18n:installer.DEV_CREW" = "macOS 版威注音の開発Shiki Suen, Isaac Xen, Hiraku Wang, など。\n威注音語彙データの維持Shiki Suen。\nウォーキング算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.DISCLAIMER_TEXT" = "免責事項vChewing Project は、OpenVanilla と協力関係や提携関係にあるわけではなく、OpenVanilla が小麦注音プロジェクトに同梱した辞書データについて、vChewing Project は一切責任負い兼ねる。特定な地政学的・観念形態的な内容は、vChewing アプリの世界的な普及に妨害する恐れがあるため、vChewing 公式辞書データに不収録。";
"i18n:installer.DO_APP_UPGRADE" = "承認と更新";
"i18n:installer.EULA_PROMPT_NOTICE" = "このアプリを実装するために、上記の条約を承認すべきである。";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音入力 実装用アプリ";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音入力 実装用アプリ";
"i18n:installer.LICENSE_TITLE" = "MIT商標不許可ライセンス (MIT-NTL License):";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "入力アプリの自動起動はうまく出来なかったかもしれません。ご自分で「システム環境設定→キーボード→入力ソース」で起動してください。";
"Install Failed" = "実装失敗。";
"Installation Successful" = "実装完了";
"OK" = "うむ";
"Stopping the old version. This may take up to one minute…" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……";
"vChewing Input Method" = "威注音入力アプリ";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音入力、利用準備完了。\n\nこのシステムユーザーアカウントで初めて実装した場合、再ログインしてください。";
"Stopping the old version. This may take up to one minute…" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……";
"Attention" = "ご注意";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "威注音入力の更新は実装完了しましたが、うまく作動できるために、このパソコンの再起動および再ログインが必要だと恐れ入ります。";
"Fatal Error" = "致命錯乱";
"Abort" = "中止";
"Cannot register input source %@ at %@." = "「%2$@」で入力アプリ「\"%1$@\"」の実装は失敗しました。";
"Cannot find input source %@ after registration." = "登録済みですが「%@」は見つけませんでした。";
"Warning" = "お知らせ";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "入力アプリの自動起動はうまく出来なかったかもしれません。ご自分で「システム環境設定→キーボード→入力ソース」で起動してください。";
"Continue" = "続行";
"i18n:installer.DISCLAIMER_TEXT" = "免責事項vChewing Project は、OpenVanilla と協力関係や提携関係にあるわけではなく、OpenVanilla が小麦注音プロジェクトに同梱した辞書データについて、vChewing Project は一切責任負い兼ねる。特定な地政学的・観念形態的な内容は、vChewing アプリの世界的な普及に妨害する恐れがあるため、vChewing 公式辞書データに不収録。";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音入力 実装用アプリ";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音入力 実装用アプリ";
"i18n:installer.ACCEPT_INSTALLATION" = "承認する";
"i18n:installer.CANCEL_INSTALLATION" = "取消";
"i18n:installer.LICENSE_TITLE" = "MIT商標不許可ライセンス (MIT-NTL License):";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.APP_DERIVED_FROM" = "曾て OpenVanilla 小麦注音プロジェクト (MIT-License) から派生。";
"i18n:installer.DEV_CREW" = "macOS 版威注音の開発Shiki Suen, Isaac Xen, Hiraku Wang, など。\n威注音語彙データの維持Shiki Suen。\nウォーキング算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.EULA_PROMPT_NOTICE" = "このアプリを実装するために、上記の条約を承認すべきである。";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……";

View File

@ -1,30 +1,30 @@
"Abort" = "放弃安装";
"Attention" = "请注意";
"vChewing Input Method" = "威注音输入法";
"i18n:installer.DO_APP_UPGRADE" = "接受并升级";
"Cancel" = "取消";
"Cannot activate the input method." = "无法启用输入法。";
"Cannot copy the file to the destination." = "无法将输入法拷贝至目的地。";
"Cannot find input source %@ after registration." = "在注册完输入法 \"%@\" 之后仍然无法找到该输入法。";
"Cannot register input source %@ at %@." = "无法从档案位置 %2$@ 安装输入法 \"%1$@\"。";
"Continue" = "继续";
"Fatal Error" = "安装错误";
"i18n:installer.ACCEPT_INSTALLATION" = "我接受";
"i18n:installer.APP_DERIVED_FROM" = "该专案曾由 OpenVanilla 小麦注音专案 (MIT-License) 衍生而来。";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "取消安装";
"i18n:installer.DEV_CREW" = "威注音 macOS 程式研发Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音词库维护Shiki Suen。\n爬轨算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.DISCLAIMER_TEXT" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音官方词库不包含任何「会在法理上妨碍威注音在全球传播」的「与地缘政治及政治意识形态有关的」内容。威注音专案与 OpenVanilla 专案之间无合作关系、无隶属关系。";
"i18n:installer.DO_APP_UPGRADE" = "接受并升级";
"i18n:installer.EULA_PROMPT_NOTICE" = "若要安装该软件,请接受上述条款。";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音输入法安装程式";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音安装程式";
"i18n:installer.LICENSE_TITLE" = "麻理去商标授权合约 (MIT-NTL License):";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "等待旧版完全停用,大约需要一分钟…";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "输入法已经安装好,但可能没有完全启用。请从「系统偏好设定」 > 「键盘」 > 「输入方式」分页加入输入法。";
"Install Failed" = "安装失败";
"Installation Successful" = "安装成功";
"OK" = "确定";
"Stopping the old version. This may take up to one minute…" = "正在试图结束正在运行的旧版输入法,大概需要一分钟…";
"vChewing Input Method" = "威注音输入法";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音输入法安装成功。\n\n若是在當前使用者帳戶內首次安裝的話請重新登入。";
"Stopping the old version. This may take up to one minute…" = "正在试图结束正在运行的旧版输入法,大概需要一分钟…";
"Attention" = "请注意";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing 安装完成,但建议您登出或重新开机,以便顺利使用新版。";
"Fatal Error" = "安装错误";
"Abort" = "放弃安装";
"Cannot register input source %@ at %@." = "无法从档案位置 %2$@ 安装输入法 \"%1$@\"。";
"Cannot find input source %@ after registration." = "在注册完输入法 \"%@\" 之后仍然无法找到该输入法。";
"Warning" = "安装不完整";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "输入法已经安装好,但可能没有完全启用。请从「系统偏好设定」 > 「键盘」 > 「输入方式」分页加入输入法。";
"Continue" = "继续";
"i18n:installer.DISCLAIMER_TEXT" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音官方词库不包含任何「会在法理上妨碍威注音在全球传播」的「与地缘政治及政治意识形态有关的」内容。威注音专案与 OpenVanilla 专案之间无合作关系、无隶属关系。";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音安装程式";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音输入法安装程式";
"i18n:installer.ACCEPT_INSTALLATION" = "我接受";
"i18n:installer.CANCEL_INSTALLATION" = "取消安装";
"i18n:installer.LICENSE_TITLE" = "麻理去商标授权合约 (MIT-NTL License):";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.APP_DERIVED_FROM" = "该专案曾由 OpenVanilla 小麦注音专案 (MIT-License) 衍生而来。";
"i18n:installer.DEV_CREW" = "威注音 macOS 程式研发Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音词库维护Shiki Suen。\n爬轨算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.EULA_PROMPT_NOTICE" = "若要安装该软件,请接受上述条款。";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "等待旧版完全停用,大约需要一分钟…";

View File

@ -1,30 +1,30 @@
"Abort" = "放棄安裝";
"Attention" = "請注意";
"vChewing Input Method" = "威注音輸入法";
"i18n:installer.DO_APP_UPGRADE" = "接受並升級";
"Cancel" = "取消";
"Cannot activate the input method." = "無法啟用輸入法。";
"Cannot copy the file to the destination." = "無法將輸入法拷貝至目的地。";
"Cannot find input source %@ after registration." = "在註冊完輸入法 \"%@\" 之後仍然無法找到該輸入法。";
"Cannot register input source %@ at %@." = "無法從檔案位置 %2$@ 安裝輸入法 \"%1$@\"。";
"Continue" = "繼續";
"Fatal Error" = "安裝錯誤";
"i18n:installer.ACCEPT_INSTALLATION" = "我接受";
"i18n:installer.APP_DERIVED_FROM" = "該專案曾由 OpenVanilla 小麥注音專案 (MIT-License) 衍生而來。";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "取消安裝";
"i18n:installer.DEV_CREW" = "威注音 macOS 程式研發Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音詞庫維護Shiki Suen。\n爬軌算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.DISCLAIMER_TEXT" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音官方詞庫不包含任何「會在法理上妨礙威注音在全球傳播」的「與地緣政治及政治意識形態有關的」內容。威註音專案與 OpenVanilla 專案之間無合作關係、無隸屬關係。";
"i18n:installer.DO_APP_UPGRADE" = "接受並升級";
"i18n:installer.EULA_PROMPT_NOTICE" = "若要安裝該軟體,請接受上述條款。";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音輸入法安裝程式";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音安裝程式";
"i18n:installer.LICENSE_TITLE" = "麻理去商標授權合約 (MIT-NTL License):";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "等待舊版完全停用,大約需要一分鐘…";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "輸入法已經安裝好,但可能沒有完全啟用。請從「系統偏好設定」 > 「鍵盤」 > 「輸入方式」分頁加入輸入法。";
"Install Failed" = "安裝失敗";
"Installation Successful" = "安裝成功";
"OK" = "確定";
"Stopping the old version. This may take up to one minute…" = "正在試圖結束正在運行的舊版輸入法,大概需要一分鐘…";
"vChewing Input Method" = "威注音輸入法";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音輸入法安裝成功。\n\n若是在當前使用者帳戶內首次安裝的話請重新登入。";
"Stopping the old version. This may take up to one minute…" = "正在試圖結束正在運行的舊版輸入法,大概需要一分鐘…";
"Attention" = "請注意";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing 安裝完成,但建議您登出或重新開機,以便順利使用新版。";
"Fatal Error" = "安裝錯誤";
"Abort" = "放棄安裝";
"Cannot register input source %@ at %@." = "無法從檔案位置 %2$@ 安裝輸入法 \"%1$@\"。";
"Cannot find input source %@ after registration." = "在註冊完輸入法 \"%@\" 之後仍然無法找到該輸入法。";
"Warning" = "安裝不完整";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "輸入法已經安裝好,但可能沒有完全啟用。請從「系統偏好設定」 > 「鍵盤」 > 「輸入方式」分頁加入輸入法。";
"Continue" = "繼續";
"i18n:installer.DISCLAIMER_TEXT" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音官方詞庫不包含任何「會在法理上妨礙威注音在全球傳播」的「與地緣政治及政治意識形態有關的」內容。威註音專案與 OpenVanilla 專案之間無合作關係、無隸屬關係。";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音安裝程式";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音輸入法安裝程式";
"i18n:installer.ACCEPT_INSTALLATION" = "我接受";
"i18n:installer.CANCEL_INSTALLATION" = "取消安裝";
"i18n:installer.LICENSE_TITLE" = "麻理去商標授權合約 (MIT-NTL License):";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.APP_DERIVED_FROM" = "該專案曾由 OpenVanilla 小麥注音專案 (MIT-License) 衍生而來。";
"i18n:installer.DEV_CREW" = "威注音 macOS 程式研發Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音詞庫維護Shiki Suen。\n爬軌算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.EULA_PROMPT_NOTICE" = "若要安裝該軟體,請接受上述條款。";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "等待舊版完全停用,大約需要一分鐘…";

View File

@ -19,4 +19,4 @@ OS_Version=$(sw_vers -productVersion)
##### fi
# Finally, register the input method:
/Users/"${login_user}"/Library/Input\ Methods/"${TARGET}".app/Contents/MacOS/"${TARGET}" install || true
/Users/"${login_user}"/Library/Input\ Methods/"${TARGET}".app/Contents/MacOS/"${TARGET}" install --all || true

View File

@ -1,33 +0,0 @@
#!/usr/bin/env swift
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
let strDataPath = "./"
func handleFiles(_ handler: @escaping ((url: URL, fileName: String)) -> Void) {
let rawURLs = FileManager.default.enumerator(at: URL(fileURLWithPath: strDataPath), includingPropertiesForKeys: nil)?.compactMap { $0 as? URL }
rawURLs?.forEach { url in
guard let fileName = url.pathComponents.last, fileName.lowercased() == "localizable.strings" else { return }
handler((url, fileName))
}
}
handleFiles { url, fileName in
guard let rawStr = try? String(contentsOf: url, encoding: .utf8) else { return }
let locale = Locale(identifier: "zh@collation=stroke")
do {
try rawStr.components(separatedBy: .newlines).filter { !$0.isEmpty }.sorted {
$0.compare($1, locale: locale) == .orderedAscending
}.joined(separator: "\n").description.appending("\n").write(to: url, atomically: false, encoding: .utf8)
} catch {
print("!! Error writing to \(fileName)")
}
}

View File

@ -11,24 +11,6 @@ BUILD_SETTINGS += ARCHS="$(ARCHS)"
BUILD_SETTINGS += ONLY_ACTIVE_ARCH=NO
endif
spmDebug:
swift build -c debug --package-path ./Packages/vChewing_MainAssembly/
spmRelease:
swift build -c release --package-path ./Packages/vChewing_MainAssembly/
spmLintFormat:
make lint --file=./Packages/Makefile || true
make format --file=./Packages/Makefile || true
spmClean:
@for currentDir in $$(ls ./Packages/); do \
if [ -d $$a ]; then \
echo "processing folder $$currentDir"; \
swift package clean --package-path ./Packages/$$currentDir || true; \
fi; \
done;
release:
xcodebuild -project vChewing.xcodeproj -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) build
@ -60,7 +42,6 @@ install-release: permission-check
.PHONY: clean
clean:
make clean --file=./Packages/Makefile || true
xcodebuild -scheme vChewingInstaller -configuration Debug $(BUILD_SETTINGS) clean
xcodebuild -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) clean
make clean --file=./Source/Data/Makefile || true

View File

@ -1 +0,0 @@
BasedOnStyle: Microsoft

View File

@ -1,104 +0,0 @@
# SwiftFormat config compliant with Google Swift Guideline
# https://google.github.io/swift/#control-flow-statements
# Specify version used in a project
--swiftversion 5.5
# Rules explicitly required by the guideline
--rules \
blankLinesAroundMark, \
blankLinesAtEndOfScope, \
blankLinesAtStartOfScope, \
blankLinesBetweenScopes, \
braces, \
consecutiveBlankLines, \
consecutiveSpaces, \
duplicateImports, \
elseOnSameLine, \
emptyBraces, \
enumNamespaces, \
extensionAccessControl, \
hoistPatternLet, \
indent, \
leadingDelimiters, \
linebreakAtEndOfFile, \
markTypes, \
organizeDeclarations, \
redundantInit, \
redundantParens, \
redundantPattern, \
redundantRawValues, \
redundantType, \
redundantVoidReturnType, \
semicolons, \
sortedImports, \
sortedSwitchCases, \
spaceAroundBraces, \
spaceAroundBrackets, \
spaceAroundComments, \
spaceAroundGenerics, \
spaceAroundOperators, \
spaceAroundParens, \
spaceInsideBraces, \
spaceInsideBrackets, \
spaceInsideComments, \
spaceInsideGenerics, \
spaceInsideParens, \
todos, \
trailingClosures, \
trailingCommas, \
trailingSpace, \
typeSugar, \
void, \
wrap, \
wrapArguments, \
wrapAttributes, \
#
#
# Additional rules not mentioned in the guideline, but helping to keep the codebase clean
# Quoting the guideline:
# Common themes among the rules in this section are:
# avoid redundancy, avoid ambiguity, and prefer implicitness over explicitness
# unless being explicit improves readability and/or reduces ambiguity.
#
#
andOperator, \
isEmpty, \
redundantBackticks, \
redundantBreak, \
redundantExtensionACL, \
redundantGet, \
redundantLetError, \
redundantNilInit, \
redundantObjc, \
redundantReturn, \
redundantSelf, \
strongifiedSelf
# Options for basic rules
--extensionacl on-declarations
--funcattributes prev-line
--indent 2
--maxwidth 100
--typeattributes prev-line
--varattributes prev-line
--voidtype tuple
--wraparguments before-first
--wrapparameters before-first
--wrapcollections before-first
--wrapreturntype if-multiline
--wrapconditions after-first
# Option for additional rules
--self init-only
# Excluded folders
--exclude Pods,**/UNTESTED_TODO,vendor,fastlane
# https://github.com/NoemiRozpara/Google-SwiftFormat-Config

View File

@ -13,13 +13,13 @@ let package = Package(
),
],
dependencies: [
.package(path: "../vChewing_OSFrameworkImpl"),
.package(path: "../vChewing_CocoaExtension"),
],
targets: [
.target(
name: "NSAttributedTextView",
dependencies: [
.product(name: "OSFrameworkImpl", package: "vChewing_OSFrameworkImpl"),
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
]
),
.testTarget(

View File

@ -6,7 +6,7 @@
// Modified by The vChewing Project in order to use it with AppKit.
import AppKit
import OSFrameworkImpl
import CocoaExtension
import SwiftUI
@available(macOS 10.15, *)

View File

@ -7,9 +7,9 @@
// requirements defined in MIT License.
import AppKit
import CocoaExtension
import Foundation
@testable import NSAttributedTextView
import OSFrameworkImpl
import Shared
import XCTest

View File

@ -1,29 +0,0 @@
+.PHONY: all
all: debug
debug:
swift build -c debug --package-path ./vChewing_MainAssembly/
release:
swift build -c release --package-path ./vChewing_MainAssembly/
clean:
@for currentDir in $$(ls ./); do \
if [ -d $$a ]; then \
echo "processing folder $$currentDir"; \
swift package clean --package-path ./$$currentDir || true; \
fi; \
done;
.PHONY: lint format
lintFormat: lint format
format:
@swiftformat --swiftversion 5.5 --indent 2 ./
lint:
@git ls-files --exclude-standard | grep -E '\.swift$$' | swiftlint --fix --autocorrect
.PHONY: permission-check install-debug install-release

View File

@ -37,7 +37,7 @@ public struct ShiftKeyUpChecker {
///
private let delayInterval = 0.2
private let delayInterval = 0.3
private var previousKeyCode: UInt16?
private var lastTime: Date = .init()

View File

@ -1,6 +1,7 @@
// (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
import Foundation
import SwiftExtension
public class LineReader {
let encoding: String.Encoding

View File

@ -1,35 +0,0 @@
// swift-tools-version: 5.7
import PackageDescription
let package = Package(
name: "BrailleSputnik",
platforms: [
.macOS(.v11),
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "BrailleSputnik",
targets: ["BrailleSputnik"]
),
],
dependencies: [
.package(path: "../vChewing_Shared"),
.package(path: "../vChewing_Tekkon"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "BrailleSputnik",
dependencies: [
.product(name: "Shared", package: "vChewing_Shared"),
.product(name: "Tekkon", package: "vChewing_Tekkon"),
]
),
.testTarget(
name: "BrailleSputnikTests",
dependencies: ["BrailleSputnik"]
),
]
)

View File

@ -1,284 +0,0 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension BrailleSputnik {
enum Braille: String {
case blank = "" // U+2800
case d1 = ""
case d2 = ""
case d12 = ""
case d3 = ""
case d13 = ""
case d23 = ""
case d123 = ""
case d4 = ""
case d14 = ""
case d24 = ""
case d124 = ""
case d34 = ""
case d134 = ""
case d234 = ""
case d1234 = ""
case d5 = ""
case d15 = ""
case d25 = ""
case d125 = ""
case d35 = ""
case d135 = ""
case d235 = ""
case d1235 = ""
case d45 = ""
case d145 = ""
case d245 = ""
case d1245 = ""
case d345 = ""
case d1345 = ""
case d2345 = ""
case d12345 = ""
case d6 = ""
case d16 = ""
case d26 = ""
case d126 = ""
case d36 = ""
case d136 = ""
case d236 = ""
case d1236 = ""
case d46 = ""
case d146 = ""
case d246 = ""
case d1246 = ""
case d346 = ""
case d1346 = ""
case d2346 = ""
case d12346 = ""
case d56 = ""
case d156 = ""
case d256 = ""
case d1256 = ""
case d356 = ""
case d1356 = ""
case d2356 = ""
case d12356 = ""
case d456 = ""
case d1456 = ""
case d2456 = ""
case d12456 = ""
case d3456 = ""
case d13456 = ""
case d23456 = ""
case d123456 = ""
}
public enum BrailleStandard: Int {
case of1947 = 1
case of2018 = 2
}
}
protocol BrailleProcessingUnit {
var mapConsonants: [String: String] { get }
var mapSemivowels: [String: String] { get }
var mapVowels: [String: String] { get }
var mapIntonations: [String: String] { get }
var mapIntonationSpecialCases: [String: String] { get }
var mapCombinedVowels: [String: String] { get }
var mapPunctuations: [String: String] { get }
func handleSpecialCases(target: inout String, value: String?) -> Bool
}
// MARK: - Static Data conforming to 1947 Standard.
extension BrailleSputnik {
class BrailleProcessingUnit1947: BrailleProcessingUnit {
func handleSpecialCases(target _: inout String, value _: String?) -> Bool {
//
false
}
let mapConsonants: [String: String] = [
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
]
let mapSemivowels: [String: String] = [
"": "", "": "", "": "",
]
let mapVowels: [String: String] = [
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
]
let mapIntonations: [String: String] = [
"˙": "⠱⠁", "ˇ": "", "ˊ": "", " ": "", "ˋ": "",
]
let mapIntonationSpecialCases: [String: String] = [
"ㄜ˙": "⠮⠁", "ㄚ˙": "⠜⠁", "ㄛ˙": "⠣⠁", "ㄣ˙": "⠥⠁",
]
let mapCombinedVowels: [String: String] = [
"ㄧㄝ": "", "ㄧㄣ": "", "ㄩㄝ": "",
"ㄨㄟ": "", "ㄨㄥ": "", "ㄨㄣ": "",
"ㄨㄚ": "", "ㄧㄡ": "", "ㄧㄤ": "",
"ㄧㄚ": "", "ㄨㄛ": "", "ㄧㄥ": "",
"ㄨㄞ": "", "ㄩㄥ": "", "ㄧㄠ": "",
"ㄧㄞ": "", "ㄨㄤ": "", "ㄩㄣ": "",
"ㄧㄢ": "", "ㄩㄢ": "", "ㄨㄢ": "",
]
let mapPunctuations: [String: String] = [
"": "⠤⠀", "·": "⠤⠀", "": "", "": "",
"": "", "": "⠕⠀", "": "⠇⠀", "": "⠒⠒",
"╴╴": "⠰⠰", "": "⠠⠤", "……": "⠐⠐⠐",
"": "⠐⠂", "—— ——": "⠐⠂⠐⠂", "": "⠈⠼", "": "⠪⠕",
"": "⠦⠦", "": "⠴⠴", "": "⠰⠤", "": "⠤⠆",
"": "⠦⠦", "": "⠴⠴", "": "⠰⠤", "": "⠤⠆",
"": "", "": "", "": "", "": "",
"": "", "": "", "": "", "": "",
]
}
}
// MARK: - Static Data conforming to 2018 Standard (GF0019-2018)
extension BrailleSputnik {
class BrailleProcessingUnit2018: BrailleProcessingUnit {
func handleSpecialCases(target: inout String, value: String?) -> Bool {
guard let value = value else { return false }
switch value {
case "": target = Braille.d2345.rawValue + Braille.d35.rawValue
case "": target = Braille.d4.rawValue + Braille.d2345.rawValue + Braille.d35.rawValue
default: return false
}
return true
}
let mapConsonants: [String: String] = [
"": Braille.d12.rawValue,
"": Braille.d1234.rawValue,
"": Braille.d134.rawValue,
"": Braille.d124.rawValue,
"": Braille.d145.rawValue,
"": Braille.d2345.rawValue,
"": Braille.d1345.rawValue,
"": Braille.d123.rawValue,
"": Braille.d1245.rawValue,
"": Braille.d13.rawValue,
"": Braille.d125.rawValue,
"": Braille.d1245.rawValue,
"": Braille.d13.rawValue,
"": Braille.d125.rawValue,
"": Braille.d34.rawValue,
"": Braille.d12345.rawValue,
"": Braille.d156.rawValue,
"": Braille.d245.rawValue,
"": Braille.d1356.rawValue,
"": Braille.d14.rawValue,
"": Braille.d234.rawValue,
]
let mapSemivowels: [String: String] = [
"": Braille.d24.rawValue,
"": Braille.d136.rawValue,
"": Braille.d346.rawValue,
]
let mapVowels: [String: String] = [
"": Braille.d35.rawValue,
"": Braille.d26.rawValue,
"": Braille.d26.rawValue,
"": Braille.d246.rawValue,
"": Braille.d2346.rawValue,
"": Braille.d235.rawValue,
"": Braille.d12356.rawValue,
"": Braille.d1236.rawValue,
"": Braille.d356.rawValue,
"": Braille.d236.rawValue,
"": Braille.d3456.rawValue, //
"": Braille.d1235.rawValue,
]
let mapIntonations: [String: String] = [
" ": Braille.d1.rawValue,
"ˊ": Braille.d2.rawValue,
"ˇ": Braille.d3.rawValue,
"ˋ": Braille.d23.rawValue,
// "˙": nil, //
]
let mapIntonationSpecialCases: [String: String] = [:]
let mapCombinedVowels: [String: String] = [
"ㄧㄚ": Braille.d1246.rawValue,
"ㄧㄝ": Braille.d15.rawValue,
"ㄧㄞ": Braille.d1246.rawValue, //
"ㄧㄠ": Braille.d345.rawValue,
"ㄧㄡ": Braille.d1256.rawValue,
"ㄧㄢ": Braille.d146.rawValue,
"ㄧㄣ": Braille.d126.rawValue,
"ㄧㄤ": Braille.d1346.rawValue,
"ㄧㄥ": Braille.d16.rawValue,
"ㄨㄚ": Braille.d123456.rawValue,
"ㄨㄛ": Braille.d135.rawValue,
"ㄨㄞ": Braille.d13456.rawValue,
"ㄨㄟ": Braille.d2456.rawValue,
"ㄨㄢ": Braille.d12456.rawValue,
"ㄨㄣ": Braille.d25.rawValue,
"ㄨㄤ": Braille.d2356.rawValue,
"ㄨㄥ": Braille.d256.rawValue,
"ㄩㄝ": Braille.d23456.rawValue,
"ㄩㄢ": Braille.d12346.rawValue,
"ㄩㄣ": Braille.d456.rawValue,
"ㄩㄥ": Braille.d1456.rawValue,
]
let mapPunctuations: [String: String] = [
"": Braille.d5.rawValue + Braille.d23.rawValue,
"·": Braille.d6.rawValue + Braille.d3.rawValue,
"": Braille.d5.rawValue,
"": Braille.d56.rawValue,
"": Braille.d4.rawValue,
"": Braille.d5.rawValue + Braille.d3.rawValue,
"": Braille.d56.rawValue + Braille.d2.rawValue,
"": Braille.d36.rawValue,
"——": Braille.d6.rawValue + Braille.d36.rawValue,
"……": Braille.d5.rawValue + Braille.d5.rawValue + Braille.d5.rawValue,
"-": Braille.d36.rawValue,
"": Braille.d5.rawValue, //
"": Braille.d2356.rawValue + Braille.d35.rawValue,
"": Braille.d5.rawValue + Braille.d36.rawValue,
"": Braille.d36.rawValue + Braille.d2.rawValue,
"": Braille.d5.rawValue + Braille.d3.rawValue,
"": Braille.d6.rawValue + Braille.d2.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d56.rawValue + Braille.d3.rawValue,
"": Braille.d6.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
// "": "", "": "", // 2018
]
}
}

View File

@ -1,109 +0,0 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Tekkon
public class BrailleSputnik {
public var standard: BrailleStandard
public init(standard: BrailleStandard) {
self.standard = standard
}
var staticData: BrailleProcessingUnit {
switch standard {
case .of1947: return Self.staticData1947
case .of2018: return Self.staticData2018
}
}
static var sharedComposer = Tekkon.Composer("", arrange: .ofDachen, correction: true)
private static let staticData1947: BrailleProcessingUnit = BrailleProcessingUnit1947()
private static let staticData2018: BrailleProcessingUnit = BrailleProcessingUnit2018()
}
public extension BrailleSputnik {
func convertToBraille(smashedPairs: [(key: String, value: String)], extraInsertion: (reading: String, cursor: Int)? = nil) -> String {
var convertedStack: [String?] = []
var processedKeysCount = 0
var extraInsertion = extraInsertion
smashedPairs.forEach { key, value in
let subKeys = key.split(separator: "\t")
switch subKeys.count {
case 0: return
case 1:
guard !key.isEmpty else { break }
let isPunctuation: Bool = key.first == "_" //
if isPunctuation {
convertedStack.append(convertPunctuationToBraille(value))
} else {
var key = key.description
fixToneOne(target: &key)
convertedStack.append(convertPhonabetReadingToBraille(key, value: value))
}
processedKeysCount += 1
default:
//
subKeys.forEach { subKey in
var subKey = subKey.description
fixToneOne(target: &subKey)
convertedStack.append(convertPhonabetReadingToBraille(subKey))
processedKeysCount += 1
}
}
if let theExtraInsertion = extraInsertion, processedKeysCount == theExtraInsertion.cursor {
convertedStack.append(convertPhonabetReadingToBraille(theExtraInsertion.reading))
extraInsertion = nil
}
}
return convertedStack.compactMap(\.?.description).joined()
}
private func fixToneOne(target key: inout String) {
for char in key {
guard Tekkon.Phonabet(char.description).type != .null else { return }
}
if let lastChar = key.last?.description, Tekkon.Phonabet(lastChar).type != .intonation {
key += " "
}
}
func convertPunctuationToBraille(_ givenTarget: any StringProtocol) -> String? {
staticData.mapPunctuations[givenTarget.description]
}
func convertPhonabetReadingToBraille(_ rawReading: any StringProtocol, value referredValue: String? = nil) -> String? {
var resultStack = ""
//
guard !staticData.handleSpecialCases(target: &resultStack, value: referredValue) else { return resultStack }
Self.sharedComposer.clear()
rawReading.forEach { char in
Self.sharedComposer.receiveKey(fromPhonabet: char.description)
}
let consonant = Self.sharedComposer.consonant.value
let semivowel = Self.sharedComposer.semivowel.value
let vowel = Self.sharedComposer.vowel.value
let intonation = Self.sharedComposer.intonation.value
if !consonant.isEmpty {
resultStack.append(staticData.mapConsonants[consonant] ?? "")
}
let combinedVowels = Self.sharedComposer.semivowel.value + Self.sharedComposer.vowel.value
if combinedVowels.count == 2 {
resultStack.append(staticData.mapCombinedVowels[combinedVowels] ?? "")
} else {
resultStack.append(staticData.mapSemivowels[semivowel] ?? "")
resultStack.append(staticData.mapVowels[vowel] ?? "")
}
// 調
if let intonationSpecialCaseMetResult = staticData.mapIntonationSpecialCases[vowel + intonation] {
resultStack.append(intonationSpecialCaseMetResult.last?.description ?? "")
} else {
resultStack.append(staticData.mapIntonations[intonation] ?? "")
}
return resultStack
}
}

View File

@ -1,28 +0,0 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
@testable import BrailleSputnik
import XCTest
final class BrailleSputnikTests: XCTestCase {
func testBrailleConversion() throws {
//
var rawReadingStr = "ㄉㄚˋ-ㄑㄧㄡ-ㄑㄧㄡ-ㄅㄧㄥˋ-ㄌㄜ˙-ㄦˋ-ㄑㄧㄡ-ㄑㄧㄡ-ㄑㄧㄠˊ-_"
rawReadingStr += "-ㄙㄢ-ㄑㄧㄡ-ㄑㄧㄡ-ㄘㄞˇ-ㄧㄠˋ-ㄙˋ-ㄑㄧㄡ-ㄑㄧㄡ-ㄠˊ-_。"
let rawReadingArray: [(key: String, value: String)] = rawReadingStr.split(separator: "-").map {
let value: String = $0.first == "_" ? $0.last?.description ?? "" : ""
return (key: $0.description, value: value)
}
let processor = BrailleSputnik(standard: .of1947)
let result1947 = processor.convertToBraille(smashedPairs: rawReadingArray)
XCTAssertEqual(result1947, "⠙⠜⠐⠚⠎⠄⠚⠎⠄⠕⠽⠐⠉⠮⠁⠱⠐⠚⠎⠄⠚⠎⠄⠚⠪⠂⠆⠑⠧⠄⠚⠎⠄⠚⠎⠄⠚⠺⠈⠪⠐⠑⠐⠚⠎⠄⠚⠎⠄⠩⠂⠤⠀")
processor.standard = .of2018
let result2018 = processor.convertToBraille(smashedPairs: rawReadingArray)
XCTAssertEqual(result2018, "⠙⠔⠆⠅⠳⠁⠅⠳⠁⠃⠡⠆⠇⠢⠗⠆⠅⠳⠁⠅⠳⠁⠅⠜⠂⠐⠎⠧⠁⠅⠳⠁⠅⠳⠁⠉⠪⠄⠜⠆⠎⠆⠅⠳⠁⠅⠳⠁⠖⠂⠐⠆")
}
}

View File

@ -19,7 +19,6 @@ public class CandidateCellData: Hashable {
public static var unifiedSize: Double = 16
public static var unifiedCharDimension: Double { ceil(unifiedSize * 1.0125 + 7) }
public static var unifiedTextHeight: Double { ceil(unifiedSize * 19 / 16) }
static var internalPrefs = PrefMgr()
public var selectionKey: String
public let displayedText: String
public private(set) var textDimension: NSSize
@ -82,8 +81,7 @@ public class CandidateCellData: Hashable {
}
public func cellLength(isMatrix: Bool = true) -> Double {
let factor: CGFloat = (Self.internalPrefs.minCellWidthForHorizontalMatrix == 0) ? 1.5 : 2
let minLength = ceil(Self.unifiedCharDimension * factor + size * 1.25)
let minLength = ceil(Self.unifiedCharDimension * 2 + size * 1.25)
if displayedText.count <= 2, isMatrix { return minLength }
return textDimension.width
}
@ -202,14 +200,14 @@ public class CandidateCellData: Hashable {
return attrStrCandidate
}
public func charDescriptions(shortened: Bool = false) -> [String] {
public var charDescriptions: [String] {
var result = displayedText
if displayedText.contains("("), displayedText.count > 2 {
result = displayedText.replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "")
}
return result.flatMap(\.unicodeScalars).compactMap {
let theName: String = $0.properties.name ?? ""
return shortened ? String(format: "U+%02X", $0.value) : String(format: "U+%02X %@", $0.value, theName)
return String(format: "U+%02X %@", $0.value, theName)
}
}

View File

@ -301,12 +301,10 @@ extension CandidatePool {
.font: Self.blankCell.phraseFont(size: reverseLookupTextSize),
]
let result = NSMutableAttributedString(string: "", attributes: attrReverseLookupSpacer)
var addedCounter = 0
for neta in reverseLookupResult {
result.append(NSAttributedString(string: " ", attributes: attrReverseLookupSpacer))
result.append(NSAttributedString(string: " \(neta) ", attributes: attrReverseLookup))
addedCounter += 1
if maxLinesPerPage == 1, addedCounter == 2 { break }
if maxLinesPerPage == 1 { break }
}
return result
}

View File

@ -17,24 +17,26 @@ open class CtlCandidate: NSWindowController, CtlCandidateProtocol {
open var reverseLookupResult: [String] = []
open func highlightedColor() -> NSColor {
var result = NSColor.clear
if #available(macOS 10.14, *) {
result = .controlAccentColor
} else {
result = .alternateSelectedControlTextColor
var result = NSColor.controlAccentColor
var colorBlendAmount: Double = NSApplication.isDarkMode ? 0.3 : 0.0
if #available(macOS 10.14, *), !NSApplication.isDarkMode, locale == "zh-Hant" {
colorBlendAmount = 0.15
}
let colorBlendAmount = 0.3
//
switch locale {
case "zh-Hans":
result = NSColor.red
result = NSColor.systemRed
case "zh-Hant":
result = NSColor.blue
result = NSColor.systemBlue
case "ja":
result = NSColor.brown
result = NSColor.systemBrown
default: break
}
let blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
var blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
if #unavailable(macOS 10.14) {
colorBlendAmount = 0.3
blendingAgainstTarget = NSColor.white
}
return result.blended(withFraction: colorBlendAmount, of: blendingAgainstTarget)!
}

View File

@ -7,7 +7,7 @@
// requirements defined in MIT License.
import AppKit
import OSFrameworkImpl
import CocoaExtension
import Shared
private extension NSUserInterfaceLayoutOrientation {
@ -108,34 +108,16 @@ public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
override open func updateDisplay() {
guard let window = window else { return }
if let currentCandidateText = Self.thePool.currentSelectedCandidateText {
reverseLookupResult = delegate?.reverseLookup(for: currentCandidateText) ?? []
Self.thePool.reverseLookupResult = reverseLookupResult
Self.thePool.tooltip = delegate?.candidateToolTip(shortened: !Self.thePool.isMatrix) ?? ""
}
delegate?.candidatePairHighlightChanged(at: highlightedIndex)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.updateNSWindowModern(window)
}
//
reverseLookupResult = []
//
if let currentCandidate = Self.thePool.currentCandidate {
let displayedText = currentCandidate.displayedText
var lookupResult: [String?] = delegate?.reverseLookup(for: displayedText) ?? []
if displayedText.count == 1, delegate?.showCodePointForCurrentCandidate ?? false {
if lookupResult.isEmpty {
lookupResult.append(currentCandidate.charDescriptions(shortened: !Self.thePool.isMatrix).first)
} else {
lookupResult.insert(currentCandidate.charDescriptions(shortened: true).first, at: lookupResult.startIndex)
}
reverseLookupResult = lookupResult.compactMap { $0 }
} else {
reverseLookupResult = lookupResult.compactMap { $0 }
// UNICODE
if !Self.thePool.isMatrix {
reverseLookupResult = [reverseLookupResult.first].compactMap { $0 }
}
}
}
Self.thePool.reverseLookupResult = reverseLookupResult
Self.thePool.tooltip = delegate?.candidateToolTip(shortened: !Self.thePool.isMatrix) ?? ""
delegate?.candidatePairHighlightChanged(at: highlightedIndex)
}
func updateNSWindowModern(_ window: NSWindow) {

View File

@ -7,7 +7,6 @@
// requirements defined in MIT License.
import AppKit
import OSFrameworkImpl
import Shared
/// AppKit SwiftUI
@ -152,17 +151,30 @@ public extension VwrCandidateTDKAppKit {
private extension VwrCandidateTDKAppKit {
private func prepareMenu() {
let newMenu = NSMenu()
newMenu.appendItems(self) {
NSMenu.Item(
verbatim: "\(clickedCell.displayedText)"
)?.act(#selector(menuActionOfBoosting(_:)))
NSMenu.Item(
verbatim: "\(clickedCell.displayedText)"
)?.act(#selector(menuActionOfNerfing(_:)))
NSMenu.Item(
verbatim: "✖︎ \(clickedCell.displayedText)"
)?.act(#selector(menuActionOfFiltering(_:)))
.nulled(!thePool.isFilterable(target: clickedCell.index))
let boostMenuItem = NSMenuItem(
title: "\(clickedCell.displayedText)",
action: #selector(menuActionOfBoosting(_:)),
keyEquivalent: ""
)
boostMenuItem.target = self
newMenu.addItem(boostMenuItem)
let nerfMenuItem = NSMenuItem(
title: "\(clickedCell.displayedText)",
action: #selector(menuActionOfNerfing(_:)),
keyEquivalent: ""
)
nerfMenuItem.target = self
newMenu.addItem(nerfMenuItem)
if thePool.isFilterable(target: clickedCell.index) {
let filterMenuItem = NSMenuItem(
title: "✖︎ \(clickedCell.displayedText)",
action: #selector(menuActionOfFiltering(_:)),
keyEquivalent: ""
)
filterMenuItem.target = self
newMenu.addItem(filterMenuItem)
}
theMenu = newMenu

View File

@ -2,23 +2,25 @@
import PackageDescription
let package = Package(
name: "OSFrameworkImpl",
name: "CocoaExtension",
platforms: [
.macOS(.v11),
],
products: [
.library(
name: "OSFrameworkImpl",
targets: ["OSFrameworkImpl"]
name: "CocoaExtension",
targets: ["CocoaExtension"]
),
],
dependencies: [
.package(path: "../vChewing_IMKUtils"),
.package(path: "../vChewing_SwiftExtension"),
],
targets: [
.target(
name: "OSFrameworkImpl",
name: "CocoaExtension",
dependencies: [
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
]
),

View File

@ -1,4 +1,4 @@
# OSFrameworkImpl
# CocoaExtension
威注音輸入法針對 Cocoa 的一些功能擴充,使程式維護體驗更佳。

View File

@ -71,18 +71,6 @@ public extension NSAttributedString {
public extension NSString {
var localized: String { NSLocalizedString(description, comment: "") }
@objc func getCharDescriptions(_: Any? = nil) -> [String] {
(self as String).charDescriptions
}
@objc func getCodePoints(_: Any? = nil) -> [String] {
(self as String).codePoints
}
@objc func getDescriptionAsCodePoints(_: Any? = nil) -> [String] {
(self as String).describedAsCodePoints
}
}
// MARK: - NSRange Extension
@ -140,14 +128,15 @@ public extension NSApplication {
// MARK: - System Dark Mode Status Detector.
static var isDarkMode: Bool {
// "NSApp" can be nil during SPM unit tests.
// Therefore, the method dedicated for macOS 10.15 and later is not considered stable anymore.
// Fortunately, the method for macOS 10.14 works well on later macOS releases.
if #available(macOS 10.14, *), let strAIS = UserDefaults.current.string(forKey: "AppleInterfaceStyle") {
return strAIS.lowercased().contains("dark")
} else {
return false
if #unavailable(macOS 10.14) { return false }
if #available(macOS 10.15, *) {
let appearanceDescription = NSApp.effectiveAppearance.debugDescription
.lowercased()
return appearanceDescription.contains("dark")
} else if let appleInterfaceStyle = UserDefaults.current.string(forKey: "AppleInterfaceStyle") {
return appleInterfaceStyle.lowercased().contains("dark")
}
return false
}
// MARK: - Tell whether this IME is running with Root privileges.
@ -210,6 +199,19 @@ public extension NSApplication {
}
}
// MARK: - String.applyingTransform
public extension String {
func applyingTransformFW2HW(reverse: Bool) -> String {
if #available(macOS 10.11, *) {
return applyingTransform(.fullwidthToHalfwidth, reverse: reverse) ?? self
}
let theString = NSMutableString(string: self)
CFStringTransform(theString, nil, kCFStringTransformFullwidthHalfwidth, reverse)
return theString as String
}
}
// MARK: - Check whether current date is the given date.
public extension Date {
@ -325,13 +327,3 @@ public extension NSApplication {
UserDefaults.standard.object(forKey: "AppleAccentColor") != nil
}
}
// MARK: - Pasteboard Type Extension.
public extension NSPasteboard.PasteboardType {
static let kUTTypeFileURL = Self(rawValue: "public.file-url") // import UniformTypeIdentifiers
static let kUTTypeData = Self(rawValue: "public.data") // import UniformTypeIdentifiers
static let kUTTypeAppBundle = Self(rawValue: "com.apple.application-bundle") // import UniformTypeIdentifiers
static let kUTTypeUTF8PlainText = Self(rawValue: "public.utf8-plain-text")
static let kNSFilenamesPboardType = Self(rawValue: "NSFilenamesPboardType")
}

View File

@ -6,103 +6,42 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import AppKit
import IMKUtils
public struct KBEvent: InputSignalProtocol, Hashable {
public private(set) var type: EventType
public private(set) var modifierFlags: ModifierFlags
public private(set) var timestamp: TimeInterval
public private(set) var windowNumber: Int
public private(set) var characters: String?
public private(set) var charactersIgnoringModifiers: String?
public private(set) var isARepeat: Bool
public private(set) var keyCode: UInt16
// MARK: - NSEvent Extension - Reconstructors
public init(
with type: KBEvent.EventType? = nil,
modifierFlags: KBEvent.ModifierFlags? = nil,
public extension NSEvent {
func reinitiate(
with type: NSEvent.EventType? = nil,
location: NSPoint? = nil,
modifierFlags: NSEvent.ModifierFlags? = nil,
timestamp: TimeInterval? = nil,
windowNumber: Int? = nil,
characters: String? = nil,
charactersIgnoringModifiers: String? = nil,
isARepeat: Bool? = nil,
keyCode: UInt16? = nil
) {
var characters = characters
checkSpecialKey: if let matchedKey = KeyCode(rawValue: keyCode ?? 0), let flags = modifierFlags {
let scalar = matchedKey.correspondedSpecialKeyScalar(flags: flags)
guard let scalar = scalar else { break checkSpecialKey }
characters = .init(scalar)
}
self.type = type ?? .keyDown
self.modifierFlags = modifierFlags ?? []
self.timestamp = timestamp ?? Date().timeIntervalSince1970
self.windowNumber = windowNumber ?? 0
self.characters = characters ?? ""
self.charactersIgnoringModifiers = charactersIgnoringModifiers ?? characters ?? ""
self.isARepeat = isARepeat ?? false
self.keyCode = keyCode ?? KeyCode.kNone.rawValue
}
public func reinitiate(
with type: KBEvent.EventType? = nil,
modifierFlags: KBEvent.ModifierFlags? = nil,
timestamp: TimeInterval? = nil,
windowNumber: Int? = nil,
characters: String? = nil,
charactersIgnoringModifiers: String? = nil,
isARepeat: Bool? = nil,
keyCode: UInt16? = nil
) -> KBEvent {
) -> NSEvent? {
let oldChars: String = text
return KBEvent(
with: type ?? .keyDown,
return NSEvent.keyEvent(
with: type ?? self.type,
location: location ?? locationInWindow,
modifierFlags: modifierFlags ?? self.modifierFlags,
timestamp: timestamp ?? self.timestamp,
windowNumber: windowNumber ?? self.windowNumber,
context: nil,
characters: characters ?? oldChars,
charactersIgnoringModifiers: charactersIgnoringModifiers ?? characters ?? oldChars,
isARepeat: isARepeat ?? self.isARepeat,
keyCode: keyCode ?? self.keyCode
)
}
}
// MARK: - KBEvent Extension - SubTypes
public extension KBEvent {
struct ModifierFlags: OptionSet, Hashable {
public init(rawValue: UInt) {
self.rawValue = rawValue
}
public let rawValue: UInt
public static let capsLock = ModifierFlags(rawValue: 1 << 16) // Set if Caps Lock key is pressed.
public static let shift = ModifierFlags(rawValue: 1 << 17) // Set if Shift key is pressed.
public static let control = ModifierFlags(rawValue: 1 << 18) // Set if Control key is pressed.
public static let option = ModifierFlags(rawValue: 1 << 19) // Set if Option or Alternate key is pressed.
public static let command = ModifierFlags(rawValue: 1 << 20) // Set if Command key is pressed.
public static let numericPad = ModifierFlags(rawValue: 1 << 21) // Set if any key in the numeric keypad is pressed.
public static let help = ModifierFlags(rawValue: 1 << 22) // Set if the Help key is pressed.
public static let function = ModifierFlags(rawValue: 1 << 23) // Set if any function key is pressed.
public static let deviceIndependentFlagsMask = ModifierFlags(rawValue: 0xFFFF_0000)
}
enum EventType: UInt8 {
case keyDown = 10
case keyUp = 11
case flagsChanged = 12
}
}
// MARK: - KBEvent Extension - Emacs Key Conversions
public extension KBEvent {
/// Emacs KBEvent KBEvent KBEvent
/// Emacs NSEvent NSEvent NSEvent
/// - Parameter isVerticalTyping:
/// - Returns:
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> KBEvent {
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> NSEvent {
guard isEmacsKey else { return self }
let newKeyCode: UInt16 = {
switch isVerticalContext {
@ -111,15 +50,32 @@ public extension KBEvent {
}
}()
guard newKeyCode != 0 else { return self }
return reinitiate(modifierFlags: [], characters: nil, charactersIgnoringModifiers: nil, keyCode: newKeyCode)
let newCharScalar: Unicode.Scalar = {
switch charCode {
case 6:
return isVerticalContext
? NSEvent.SpecialKey.downArrow.unicodeScalar : NSEvent.SpecialKey.rightArrow.unicodeScalar
case 2:
return isVerticalContext
? NSEvent.SpecialKey.upArrow.unicodeScalar : NSEvent.SpecialKey.leftArrow.unicodeScalar
case 1: return NSEvent.SpecialKey.home.unicodeScalar
case 5: return NSEvent.SpecialKey.end.unicodeScalar
case 4: return NSEvent.SpecialKey.deleteForward.unicodeScalar // Use "deleteForward" for PC delete.
case 22: return NSEvent.SpecialKey.pageDown.unicodeScalar
default: return .init(0)
}
}()
let newChar = String(newCharScalar)
return reinitiate(modifierFlags: [], characters: newChar, charactersIgnoringModifiers: newChar, keyCode: newKeyCode)
?? self
}
}
// MARK: - KBEvent Extension - InputSignalProtocol
// MARK: - NSEvent Extension - InputSignalProtocol
public extension KBEvent {
public extension NSEvent {
var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
/// KBEvent.characters
/// NSEvent.characters
/// - Remark: event.type == .flagsChanged
/// event.characters? NSInternalInconsistencyException
var text: String { isFlagChanged ? "" : characters ?? "" }
@ -142,6 +98,10 @@ public extension KBEvent {
modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
}
static var keyModifierFlags: ModifierFlags {
Self.modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
}
var isFlagChanged: Bool { type == .flagsChanged }
var isEmacsKey: Bool {
@ -244,94 +204,6 @@ public extension KBEvent {
// MARK: - Enums of Constants
public extension KBEvent {
enum SpecialKey: UInt16 {
var unicodeScalar: Unicode.Scalar { .init(rawValue) ?? .init(0) }
case upArrow = 0xF700
case downArrow = 0xF701
case leftArrow = 0xF702
case rightArrow = 0xF703
case f1 = 0xF704
case f2 = 0xF705
case f3 = 0xF706
case f4 = 0xF707
case f5 = 0xF708
case f6 = 0xF709
case f7 = 0xF70A
case f8 = 0xF70B
case f9 = 0xF70C
case f10 = 0xF70D
case f11 = 0xF70E
case f12 = 0xF70F
case f13 = 0xF710
case f14 = 0xF711
case f15 = 0xF712
case f16 = 0xF713
case f17 = 0xF714
case f18 = 0xF715
case f19 = 0xF716
case f20 = 0xF717
case f21 = 0xF718
case f22 = 0xF719
case f23 = 0xF71A
case f24 = 0xF71B
case f25 = 0xF71C
case f26 = 0xF71D
case f27 = 0xF71E
case f28 = 0xF71F
case f29 = 0xF720
case f30 = 0xF721
case f31 = 0xF722
case f32 = 0xF723
case f33 = 0xF724
case f34 = 0xF725
case f35 = 0xF726
case insert = 0xF727
case deleteForward = 0xF728
case home = 0xF729
case begin = 0xF72A
case end = 0xF72B
case pageUp = 0xF72C
case pageDown = 0xF72D
case printScreen = 0xF72E
case scrollLock = 0xF72F
case pause = 0xF730
case sysReq = 0xF731
case `break` = 0xF732
case reset = 0xF733
case stop = 0xF734
case menu = 0xF735
case user = 0xF736
case system = 0xF737
case print = 0xF738
case clearLine = 0xF739
case clearDisplay = 0xF73A
case insertLine = 0xF73B
case deleteLine = 0xF73C
case insertCharacter = 0xF73D
case deleteCharacter = 0xF73E
case prev = 0xF73F
case next = 0xF740
case select = 0xF741
case execute = 0xF742
case undo = 0xF743
case redo = 0xF744
case find = 0xF745
case help = 0xF746
case modeSwitch = 0xF747
case enter = 0x03
case backspace = 0x08
case tab = 0x09
case newline = 0x0A
case formFeed = 0x0C
case carriageReturn = 0x0D
case backTab = 0x19
case delete = 0x7F
case lineSeparator = 0x2028
case paragraphSeparator = 0x2029
}
}
// Use KeyCodes as much as possible since its recognition won't be affected by macOS Base Keyboard Layouts.
// KeyCodes: https://eastmanreference.com/complete-list-of-applescript-key-codes
// Also: HIToolbox.framework/Versions/A/Headers/Events.h
@ -393,79 +265,14 @@ public enum KeyCode: UInt16 {
case kDownArrow = 125
case kUpArrow = 126
public func toKBEvent() -> KBEvent {
.init(
modifierFlags: [],
timestamp: TimeInterval(), windowNumber: 0,
public func toEvent() -> NSEvent? {
NSEvent.keyEvent(
with: .keyDown, location: .zero, modifierFlags: [],
timestamp: TimeInterval(), windowNumber: 0, context: nil,
characters: "", charactersIgnoringModifiers: "",
isARepeat: false, keyCode: rawValue
)
}
public func correspondedSpecialKeyScalar(flags: KBEvent.ModifierFlags) -> Unicode.Scalar? {
var rawData: KBEvent.SpecialKey? {
switch self {
case .kNone: return nil
case .kCarriageReturn: return .carriageReturn
case .kTab:
return flags.contains(.shift) ? .backTab : .tab
case .kSpace: return nil
case .kSymbolMenuPhysicalKeyIntl: return nil
case .kBackSpace: return .backspace
case .kEscape: return nil
case .kCommand: return nil
case .kShift: return nil
case .kCapsLock: return nil
case .kOption: return nil
case .kControl: return nil
case .kRightShift: return nil
case .kRightOption: return nil
case .kRightControl: return nil
case .kFunction: return nil
case .kF17: return .f17
case .kVolumeUp: return nil
case .kVolumeDown: return nil
case .kMute: return nil
case .kLineFeed: return nil // TODO: return
case .kF18: return .f18
case .kF19: return .f19
case .kF20: return .f20
case .kYen: return nil
case .kSymbolMenuPhysicalKeyJIS: return nil
case .kJISNumPadComma: return nil
case .kF5: return .f5
case .kF6: return .f6
case .kF7: return .f7
case .kF3: return .f7
case .kF8: return .f8
case .kF9: return .f9
case .kJISAlphanumericalKey: return nil
case .kF11: return .f11
case .kJISKanaSwappingKey: return nil
case .kF13: return .f13
case .kF16: return .f16
case .kF14: return .f14
case .kF10: return .f10
case .kContextMenu: return .menu
case .kF12: return .f12
case .kF15: return .f15
case .kHelp: return .help
case .kHome: return .home
case .kPageUp: return .pageUp
case .kWindowsDelete: return .deleteForward
case .kF4: return .f4
case .kEnd: return .end
case .kF2: return .f2
case .kPageDown: return .pageDown
case .kF1: return .f1
case .kLeftArrow: return .leftArrow
case .kRightArrow: return .rightArrow
case .kDownArrow: return .downArrow
case .kUpArrow: return .upArrow
}
}
return rawData?.unicodeScalar
}
}
enum KeyCodeBlackListed: UInt16 {
@ -506,6 +313,17 @@ let mapMainAreaNumKey: [UInt16: String] = [
/// 95 Key Code JIS
let arrNumpadKeyCodes: [UInt16] = [65, 67, 69, 71, 75, 78, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 92, 95]
// CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html
enum CharCode: UInt16 {
case yajuusenpaiA = 114
case yajuusenpaiB = 514
case yajuusenpaiC = 1919
case yajuusenpaiD = 810
// CharCode is not reliable at all. KeyCode is the most appropriate choice due to its accuracy.
// KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts ...
// ... but only focuses on which physical key is pressed.
}
// MARK: - Emacs CharCode-KeyCode translation tables.
public enum EmacsKey {
@ -515,17 +333,17 @@ public enum EmacsKey {
// MARK: - Apple ABC Keyboard Mapping
public extension KBEvent {
func layoutTranslated(to layout: LatinKeyboardMappings = .qwerty) -> KBEvent {
public extension NSEvent {
func layoutTranslated(to layout: LatinKeyboardMappings = .qwerty) -> NSEvent {
let mapTable = layout.mapTable
if isFlagChanged { return self }
guard keyModifierFlags == .shift || keyModifierFlags.isEmpty else { return self }
if !mapTable.keys.contains(keyCode) { return self }
guard let dataTuplet = mapTable[keyCode] else { return self }
let result: KBEvent = reinitiate(
let result: NSEvent? = reinitiate(
characters: isShiftHold ? dataTuplet.1 : dataTuplet.0,
charactersIgnoringModifiers: dataTuplet.0
)
return result
return result ?? self
}
}

View File

@ -7,6 +7,7 @@
// requirements defined in MIT License.
import AppKit
import InputMethodKit
public extension NSWindowController {
func orderFront() {
@ -48,19 +49,12 @@ public extension NSWindowController {
}
public extension NSWindow {
@discardableResult func callAlert(title: String, text: String? = nil) -> NSApplication.ModalResponse {
(self as NSWindow?).callAlert(title: title, text: text)
}
}
public extension NSWindow? {
@discardableResult func callAlert(title: String, text: String? = nil) -> NSApplication.ModalResponse {
let alert = NSAlert()
alert.messageText = title
if let text = text { alert.informativeText = text }
alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
var result: NSApplication.ModalResponse = .alertFirstButtonReturn
guard let self = self else { return alert.runModal() }
alert.beginSheetModal(for: self) { theResponce in
result = theResponce
}

View File

@ -0,0 +1,77 @@
// (c) 2023 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
/// A Swift script to check whether a non-system process is abusing the SecureEventInput.
import AppKit
import IOKit
import Quartz
public enum SecureEventInputSputnik {
public static func getIORegListResults() -> String? {
var resultDictionaryCF: Unmanaged<CFMutableDictionary>?
/// Regarding the parameter in IORegistryGetRootEntry:
/// Both kIOMasterPortDefault and kIOMainPortDefault are 0.
/// The latter one is similar to what `git` had done: changing "Master" to "Main".
let statusSucceeded = IORegistryEntryCreateCFProperties(
IORegistryGetRootEntry(0), &resultDictionaryCF, kCFAllocatorDefault, IOOptionBits(0)
)
guard statusSucceeded == KERN_SUCCESS else { return nil }
let dict = resultDictionaryCF?.takeRetainedValue()
guard let dict: [CFString: Any] = dict as? [CFString: Any] else { return nil }
return (dict.description)
}
/// Find all non-system processes using the SecureEventInput.
/// - Parameter abusersOnly: List only non-frontmost processes.
/// - **Reason to Use**: Non-frontmost processes of such are considered abusers of SecureEventInput,
/// hindering 3rd-party input methods from being switched to by the user.
/// They are also hindering users from accessing the menu of all 3rd-party input methods.
/// There are Apple's internal business reasons why macOS always has lack of certain crucial input methods,
/// plus that some some IMEs in macOS have certain bugs / defects for decades and are unlikely to be solved,
/// making the sense that why there are needs of 3rd-party input methods.
/// - **How to Use**: For example, one can use an NSTimer to run this function
/// with `abusersOnly: true` every 15~60 seconds. Once the result dictionary is not empty,
/// you may either warn the users to restart the matched process or directly terminate it.
/// Note that you cannot terminate a process if your app is Sandboxed.
/// - Returns: Matched results as a dictionary in `[Int32: NSRunningApplication]` format. The keys are PIDs.
/// - Remark: The`"com.apple.SecurityAgent"` won't be included in the result since it is a system process.
/// Also, "com.apple.loginwindow" should be excluded as long as the system screen saver engine is running.
public static func getRunningSecureInputApps(abusersOnly: Bool = false) -> [Int32: NSRunningApplication] {
var result = [Int32: NSRunningApplication]()
guard let rawData = getIORegListResults() else { return result }
rawData.enumerateLines { currentLine, _ in
guard currentLine.contains("kCGSSessionSecureInputPID") else { return }
guard let filteredNumStr = Int32(currentLine.filter("0123456789".contains)) else { return }
guard let matchedApp = NSRunningApplication(processIdentifier: filteredNumStr) else { return }
guard matchedApp.bundleIdentifier != "com.apple.SecurityAgent" else { return }
guard !(matchedApp.isLoginWindowWithLockedScreenOrScreenSaver) else { return }
if abusersOnly {
guard !matchedApp.isActive else { return }
}
result[filteredNumStr] = matchedApp
}
return result
}
}
extension NSRunningApplication {
public var isLoginWindowWithLockedScreenOrScreenSaver: Bool {
guard bundleIdentifier == "com.apple.loginwindow" else { return false }
return Self.isScreenSaverEngineRunning || Self.isDesktopLocked
}
private static var isScreenSaverEngineRunning: Bool {
!NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.ScreenSaver.Engine").isEmpty
}
private static var isDesktopLocked: Bool {
guard let x = CGSessionCopyCurrentDictionary() as? [String: Any] else { return false }
return x.keys.contains("CGSSessionScreenIsLocked")
}
}

View File

@ -80,7 +80,6 @@ public class HotenkaChineseConverter {
ptrSQL = nil
return
}
sqlite3_exec(ptrSQL, "PRAGMA journal_mode = OFF;", nil, nil, nil)
}
public init(plistDir: String) {
@ -100,7 +99,9 @@ public class HotenkaChineseConverter {
dictFiles = .init()
do {
let rawData = try Data(contentsOf: URL(fileURLWithPath: jsonDir))
let rawJSON = try JSONDecoder().decode([String: [String: String]].self, from: rawData)
guard let rawJSON: [String: [String: String]] = try JSONSerialization.jsonObject(with: rawData) as? [String: [String: String]] else {
throw NSError()
}
dict = rawJSON
} catch {
NSLog("// Exception happened when reading dict json at: \(jsonDir).")

View File

@ -40,10 +40,7 @@ extension HotenkaTests {
let testInstance: HotenkaChineseConverter = .init(dictDir: testDataPath)
NSLog("// Loading complete. Generating json dict file.")
do {
let urlOutput = URL(fileURLWithPath: testDataPath + "convdict.json")
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
try encoder.encode(testInstance.dict).write(to: urlOutput, options: .atomic)
try JSONSerialization.data(withJSONObject: testInstance.dict, options: .sortedKeys).write(to: URL(fileURLWithPath: testDataPath + "convdict.json"))
} catch {
NSLog("// Error on writing strings to file: \(error)")
}

View File

@ -51,10 +51,6 @@ extension HotenkaTests {
sqlite3_exec(ptrSQL, "PRAGMA synchronous = OFF;", nil, nil, nil) == SQLITE_OK,
"HOTENKA: SQLite synchronous OFF failed."
)
XCTAssertTrue(
sqlite3_exec(ptrSQL, "PRAGMA journal_mode = OFF;", nil, nil, nil) == SQLITE_OK,
"HOTENKA: SQLite journal_mode OFF failed."
)
let sqlMakeTableHotenka = """
DROP TABLE IF EXISTS DATA_HOTENKA;

View File

@ -15,13 +15,23 @@ public enum IMKHelper {
///
/// SwiftUI 便使
public static let arrWhitelistedKeyLayoutsASCII: [String] = {
var results = LatinKeyboardMappings.allCases
if #available(macOS 10.13, *) {
results = results.filter {
![.qwertyUS, .qwertzGerman, .azertyFrench].contains($0)
}
var result = [
"com.apple.keylayout.ABC",
"com.apple.keylayout.ABC-AZERTY",
"com.apple.keylayout.ABC-QWERTZ",
"com.apple.keylayout.British",
"com.apple.keylayout.Colemak",
"com.apple.keylayout.Dvorak",
"com.apple.keylayout.Dvorak-Left",
"com.apple.keylayout.DVORAK-QWERTYCMD",
"com.apple.keylayout.Dvorak-Right",
]
if #unavailable(macOS 10.13) {
result.append("com.apple.keylayout.US")
result.append("com.apple.keylayout.German")
result.append("com.apple.keylayout.French")
}
return results.map(\.rawValue)
return result
}()
public static let arrDynamicBasicKeyLayouts: [String] = [
@ -39,29 +49,31 @@ public enum IMKHelper {
"org.unknown.keylayout.vChewingMiTAC",
]
public static var allowedAlphanumericalTISInputSources: [TISInputSource.KeyboardLayout] {
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
return arrWhitelistedKeyLayoutsASCII.compactMap { allTISKeyboardLayouts[$0] }
public static var allowedAlphanumericalTISInputSources: [TISInputSource] {
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
}
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource.KeyboardLayout?] {
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
//
var containerA: [TISInputSource.KeyboardLayout?] = []
var containerB: [TISInputSource.KeyboardLayout?] = []
var containerC: [TISInputSource.KeyboardLayout] = []
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
//
var containerA: [TISInputSource?] = []
var containerB: [TISInputSource?] = []
var containerC: [TISInputSource?] = []
let filterSet = Array(Set(arrWhitelistedKeyLayoutsASCII).subtracting(Set(arrDynamicBasicKeyLayouts)))
let matchedGroupBasic = (arrWhitelistedKeyLayoutsASCII + arrDynamicBasicKeyLayouts).compactMap {
allTISKeyboardLayouts[$0]
}
matchedGroupBasic.forEach { neta in
if filterSet.contains(neta.id) {
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
Self.arrWhitelistedKeyLayoutsASCII.forEach {
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
containerC.append(neta)
} else if neta.id.hasPrefix("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
}
Self.arrDynamicBasicKeyLayouts.forEach {
if let neta = rawDictionary[$0] {
if neta.identifier.contains("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
}
}

View File

@ -8,27 +8,25 @@
import Foundation
public enum LatinKeyboardMappings: String, CaseIterable {
public enum LatinKeyboardMappings: String {
case qwerty = "com.apple.keylayout.ABC"
case qwertyBritish = "com.apple.keylayout.British"
case qwertyUS = "com.apple.keylayout.US" // 10.9 - 10.12
case qwertyUS = "com.apple.keylayout.US"
case azerty = "com.apple.keylayout.ABC-AZERTY"
case azertyFrench = "com.apple.keylayout.French"
case qwertz = "com.apple.keylayout.ABC-QWERTZ"
case azertyFrench = "com.apple.keylayout.French" // 10.9 - 10.12
case qwertzGerman = "com.apple.keylayout.German" // 10.9 - 10.12
case qwertyGerman = "com.apple.keylayout.German"
case colemak = "com.apple.keylayout.Colemak"
case dvorak = "com.apple.keylayout.Dvorak"
case dvorakQwertyCMD = "com.apple.keylayout.DVORAK-QWERTYCMD"
case dvorakLeft = "com.apple.keylayout.Dvorak-Left"
case dvorakRight = "com.apple.keylayout.Dvorak-Right"
public var mapTable: [UInt16: (String, String)] {
switch self {
case .qwerty, .qwertyUS, .qwertyBritish: return Self.dictQwerty
case .qwerty, .qwertyUS: return Self.dictQwerty
case .azerty, .azertyFrench: return Self.dictAzerty
case .qwertz, .qwertzGerman: return Self.dictQwertz
case .qwertz, .qwertyGerman: return Self.dictQwertz
case .colemak: return Self.dictColemak
case .dvorak, .dvorakQwertyCMD: return Self.dictDvorak
case .dvorak: return Self.dictDvorak
case .dvorakLeft: return Self.dictDvorakLeft
case .dvorakRight: return Self.dictDvorakRight
}

View File

@ -12,13 +12,8 @@ import InputMethodKit
// MARK: - TISInputSource Extension by The vChewing Project (MIT-NTL License).
public extension TISInputSource {
struct KeyboardLayout: Identifiable {
public var id: String
public var titleLocalized: String
}
static var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
TISInputSource.match(modeIDs: TISInputSource.modes)
TISInputSource.modes.compactMap { TISInputSource.generate(from: $0) }
}
static var modes: [String] {
@ -27,7 +22,7 @@ public extension TISInputSource {
else {
return []
}
return tsInputModeListKey.keys.map(\.description)
return tsInputModeListKey.keys.map { $0 }
}
@discardableResult static func registerInputMethod() -> Bool {
@ -85,6 +80,10 @@ public extension TISInputSource {
== kCFBooleanTrue
}
static func generate(from identifier: String) -> TISInputSource? {
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
}
var inputModeID: String {
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputModeID), to: NSString.self) as String? ?? ""
}
@ -121,34 +120,9 @@ public extension TISInputSource {
return unsafeBitCast(r, to: NSString.self).integerValue as Int? ?? 0
}
// Refactored by Shiki Suen.
static func match(identifiers: [String] = [], modeIDs: [String] = [], onlyASCII: Bool = false) -> [TISInputSource] {
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
]
let cfDict = !onlyASCII ? nil : dicConditions as CFDictionary
var resultStack: [TISInputSource] = []
let unionedIDs = NSOrderedSet(array: modeIDs + identifiers).compactMap { $0 as? String }
let retrieved = (TISCreateInputSourceList(cfDict, true)?.takeRetainedValue() as? [TISInputSource]) ?? []
retrieved.forEach { tis in
unionedIDs.forEach { id in
guard tis.identifier == id || tis.inputModeID == id else { return }
if onlyASCII {
guard tis.scriptCode == 0 else { return }
}
resultStack.append(tis)
}
}
// retrieved
return unionedIDs.compactMap { currentIdentifier in
retrieved.first { $0.identifier == currentIdentifier || $0.inputModeID == currentIdentifier }
}
}
/// Mzp .match()
static func rawTISInputSources(onlyASCII: Bool = false) -> [TISInputSource] {
static func rawTISInputSources(onlyASCII: Bool = false) -> [String: TISInputSource] {
// CFDictionary
//
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
@ -158,21 +132,10 @@ public extension TISInputSource {
if onlyASCII {
result = result.filter { $0.scriptCode == 0 }
}
return result
}
/// Derived from rawTISInputSources().
static func getAllTISInputKeyboardLayoutMap() -> [String: TISInputSource.KeyboardLayout] {
// CFDictionary
let dicConditions: [CFString: Any] = [kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString]
//
let result = TISCreateInputSourceList(dicConditions as CFDictionary, true)?.takeRetainedValue() as? [TISInputSource] ?? .init()
var resultDictionary: [String: TISInputSource.KeyboardLayout] = [:]
var resultDictionary: [String: TISInputSource] = [:]
result.forEach {
let newNeta1 = TISInputSource.KeyboardLayout(id: $0.inputModeID, titleLocalized: $0.vChewingLocalizedName)
let newNeta2 = TISInputSource.KeyboardLayout(id: $0.identifier, titleLocalized: $0.vChewingLocalizedName)
resultDictionary[$0.inputModeID] = newNeta1
resultDictionary[$0.identifier] = newNeta2
resultDictionary[$0.inputModeID] = $0
resultDictionary[$0.identifier] = $0
}
return resultDictionary
}

View File

@ -1,212 +0,0 @@
---
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: "^ IWYU pragma:"
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: "^<.*"
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: ".*"
Priority: 3
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: "([-_](test|unittest))?$"
IncludeIsMainSourceRegex: ""
IndentAccessModifiers: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- "c++"
- "C++"
- "cs"
CanonicalDelimiter: ""
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
- ParseTestProto
- ParsePartialTestProto
CanonicalDelimiter: pb
BasedOnStyle: google
ReferenceAlignment: Pointer
ReflowComments: true
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
---

View File

@ -1,8 +0,0 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -1,31 +0,0 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "KimoDataReader",
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "KimoDataReader",
targets: ["KimoDataReader"]
),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "ObjcKimoCommunicator",
publicHeadersPath: "include"
),
.target(
name: "KimoDataReader",
dependencies: ["ObjcKimoCommunicator"]
),
.testTarget(
name: "KimoDataReaderTests",
dependencies: ["KimoDataReader"]
),
]
)

View File

@ -1,19 +0,0 @@
# KimoCommunicator
用來與奇摩輸入法進行 NSConnection 通訊的模組,便於直接從奇摩輸入法讀入使用者自訂詞資料庫的資料。
> 免責聲明:
> 與奇摩輸入法有關的原始碼是由 Yahoo 奇摩以 `SPDX Identifier: BSD-3-Clause` 釋出的,
> 但敝模組只是藉由其 Protocol API 與該當程式進行跨執行緒通訊,所以屬於合理使用範圍。
```
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
```
$ EOF.

View File

@ -1,25 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import ObjcKimoCommunicator
public class KimoCommunicator: ObjcKimoCommunicator {
public static let shared: KimoCommunicator = .init()
public func prepareData(handler: @escaping (_ key: String, _ value: String) -> Void) {
guard KimoCommunicator.shared.establishConnection() else { return }
assert(KimoCommunicator.shared.hasValidConnection())
let loopAmount = KimoCommunicator.shared.userPhraseDBTotalAmountOfRows()
for i in 0 ..< loopAmount {
let fetched = KimoCommunicator.shared.userPhraseDBDictionary(atRow: i)
guard let key = fetched["BPMF"], let text = fetched["Text"] else { continue }
handler(key, text)
}
}
}

View File

@ -1,86 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
//
// Yahoo `SPDX Identifier: BSD-3-Clause`
// Protocol API 使
#import "KimoCommunicator.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#define kYahooKimoDataObjectConnectionName @"YahooKeyKeyService"
@implementation ObjcKimoCommunicator {
id _xpcConnection;
}
///
- (void)dealloc {
[self disconnect];
}
///
- (void)disconnect {
_xpcConnection = nil;
}
///
- (bool)establishConnection {
// 2012 NSXPCConnection
// NSXPCConnection 使 NSXPCConnection
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
_xpcConnection = [NSConnection rootProxyForConnectionWithRegisteredName:
kYahooKimoDataObjectConnectionName
host:nil];
#pragma GCC diagnostic pop
BOOL result = false;
if (_xpcConnection) {
result = true;
}
if (result) {
[_xpcConnection setProtocolForProxy:@protocol(KimoUserDataReaderService)];
NSLog(@"vChewingDebug: Connection successful. Available data amount: %d.\n",
[_xpcConnection userPhraseDBNumberOfRow]);
}
return result;
}
///
- (bool)hasValidConnection {
BOOL result = false;
if (_xpcConnection) result = true;
return result;
}
- (BOOL)userPhraseDBCanProvideService {
return [self hasValidConnection]
? [_xpcConnection userPhraseDBCanProvideService]
: NO;
}
- (int)userPhraseDBTotalAmountOfRows {
return [self hasValidConnection] ? [_xpcConnection userPhraseDBNumberOfRow]
: 0;
}
- (NSDictionary<NSString*, NSString*> *)userPhraseDBDictionaryAtRow:(int)row {
return [self hasValidConnection]
? [_xpcConnection userPhraseDBDictionaryAtRow:row]
: [NSDictionary alloc];
}
- (bool)exportUserPhraseDBToFile:(NSString *)path {
return [self hasValidConnection]
? [_xpcConnection exportUserPhraseDBToFile:path]
: NO;
}
@end

View File

@ -1,46 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
// 免責聲明:
// 與奇摩輸入法有關的原始碼是由 Yahoo 奇摩以 `SPDX Identifier: BSD-3-Clause` 釋出的,
// 但敝模組只是藉由其 Protocol API 與該當程式進行跨執行緒通訊,所以屬於合理使用範圍。
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol KimoUserDataReaderService
- (BOOL)userPhraseDBCanProvideService;
- (int)userPhraseDBNumberOfRow;
- (NSDictionary *)userPhraseDBDictionaryAtRow:(int)row;
- (bool)exportUserPhraseDBToFile:(NSString *)path;
@end
/// 不要理會 Xcode 對 NSDistantObject 的過期狗吠。
/// 奇摩輸入法是用 NSConnection 寫的,
/// 換用 NSXPCConnection 只會製造更多的問題。
@interface ObjcKimoCommunicator : NSObject
/// 嘗試連線。
- (bool)establishConnection;
/// 偵測連線是否有效。
- (bool)hasValidConnection;
/// 斷開連線。
- (void)disconnect;
// Conforming KimoUserDataReaderService protocol.
- (BOOL)userPhraseDBCanProvideService;
- (int)userPhraseDBTotalAmountOfRows;
- (NSDictionary<NSString*, NSString*> *)userPhraseDBDictionaryAtRow:(int)row;
- (bool)exportUserPhraseDBToFile:(NSString *)path;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,10 +0,0 @@
@testable import ObjcKimoCommunicator
import XCTest
final class KimoDataReaderTests: XCTestCase {
//
func testExample() throws {
let shared = ObjcKimoCommunicator()
print(shared.establishConnection())
}
}

View File

@ -1,6 +0,0 @@
find . -regex '.*\.\(hh\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(cc\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(mm\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(h\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(c\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(m\)' -exec clang-format -style=file -i {} \;

View File

@ -15,7 +15,8 @@ let package = Package(
dependencies: [
.package(path: "../RMJay_LineReader"),
.package(path: "../vChewing_Megrez"),
.package(path: "../vChewing_SwiftExtension"),
.package(path: "../vChewing_PinyinPhonaConverter"),
.package(path: "../vChewing_Shared"),
],
targets: [
.target(
@ -23,7 +24,12 @@ let package = Package(
dependencies: [
.product(name: "LineReader", package: "RMJay_LineReader"),
.product(name: "Megrez", package: "vChewing_Megrez"),
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
.product(name: "Shared", package: "vChewing_Shared"),
.product(name: "PinyinPhonaConverter", package: "vChewing_PinyinPhonaConverter"),
],
resources: [
.process("Resources/sequenceDataFromEtenDOS-chs.json"),
.process("Resources/sequenceDataFromEtenDOS-cht.json"),
]
),
.testTarget(

View File

@ -1,17 +1,17 @@
# LangModelAssembly
威注音輸入法的語言模組總成套裝,以 LMAssembly 命名空間承載下述唯二對外物件:
威注音輸入法的語言模組總成套裝
- vChewingLM總命名空間也承載一些在套裝內共用的工具函式。
- LMConsolidator自動格式整理模組。
- LMInstantiator語言模組副本化模組亦集成一些自身功能擴展。
LMAssembly 總命名空間也承載一些在套裝內共用的工具函式。
- LMInstantiator語言模組副本化模組。另有其日期時間擴充模組可用對 CIN 磁帶模式無效)。
以下是子模組:
- LMAssociates關聯詞語模組。
- lmCassette專門用來處理 CIN 磁帶檔案的模組,命名為「遠野」引擎。
- LMAssociates聯想詞模組。
- LMCoreEX可以直接讀取 TXT 格式的帶有權重資料的語彙檔案的模組。
- LMCoreJSON專門用來讀取原廠 JSON 檔案的模組。
- lmPlainBopomofo專門用來讀取使用者自訂ㄅ半候選字順序覆蓋定義檔案plist的模組。
- lmReplacements專門用來讀取使用者語彙置換模式的辭典資料的模組。
- lmUserOverride半衰記憶模組。

View File

@ -11,31 +11,29 @@ import Foundation
/// InputToken.parse Token
/// Token .translated()
extension LMAssembly {
enum InputToken {
case timeZone(shortened: Bool)
case timeNow(shortened: Bool)
case date(dayDelta: Int = 0, yearDelta: Int = 0, shortened: Bool = true, luna: Bool = false)
case week(dayDelta: Int = 0, shortened: Bool = true)
case year(yearDelta: Int = 0)
case yearGanzhi(yearDelta: Int = 0)
case yearZodiac(yearDelta: Int = 0)
}
public enum InputToken {
case timeZone(shortened: Bool)
case timeNow(shortened: Bool)
case date(dayDelta: Int = 0, yearDelta: Int = 0, shortened: Bool = true, luna: Bool = false)
case week(dayDelta: Int = 0, shortened: Bool = true)
case year(yearDelta: Int = 0)
case yearGanzhi(yearDelta: Int = 0)
case yearZodiac(yearDelta: Int = 0)
}
// MARK: - 使 API
public extension String {
func parseAsInputToken(isCHS: Bool) -> [String] {
LMAssembly.InputToken.parse(from: self).map { $0.translated(isCHS: isCHS) }.flatMap { $0 }.deduplicated
InputToken.parse(from: self).map { $0.translated(isCHS: isCHS) }.flatMap { $0 }.deduplicated
}
}
// MARK: - Parser parsing raw token value to construct token.
extension LMAssembly.InputToken {
static func parse(from rawToken: String) -> [LMAssembly.InputToken] {
var result: [LMAssembly.InputToken] = []
public extension InputToken {
static func parse(from rawToken: String) -> [InputToken] {
var result: [InputToken] = []
guard rawToken.prefix(6) == "MACRO@" else { return result }
var mapParams: [String: Int] = [:]
let tokenComponents = rawToken.dropFirst(6).split(separator: "_").map { param in
@ -71,7 +69,7 @@ extension LMAssembly.InputToken {
// MARK: - Parser parsing token itself.
extension LMAssembly.InputToken {
public extension InputToken {
func translated(isCHS: Bool) -> [String] {
let locale = Locale(identifier: isCHS ? "zh-Hans" : "zh-Hant-TW")
let formatter = DateFormatter()

View File

@ -8,8 +8,9 @@
import Foundation
import LineReader
import Shared
public extension LMAssembly {
public extension vChewingLM {
enum LMConsolidator {
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
@ -25,19 +26,19 @@ public extension LMAssembly {
let lineReader = try LineReader(file: fileHandle)
for strLine in lineReader { // i=0
if strLine != kPragmaHeader {
vCLMLog("Header Mismatch, Starting In-Place Consolidation.")
vCLog("Header Mismatch, Starting In-Place Consolidation.")
return false
} else {
vCLMLog("Header Verification Succeeded: \(strLine).")
vCLog("Header Verification Succeeded: \(strLine).")
return true
}
}
} catch {
vCLMLog("Header Verification Failed: File Access Error.")
vCLog("Header Verification Failed: File Access Error.")
return false
}
}
vCLMLog("Header Verification Failed: File Missing.")
vCLog("Header Verification Failed: File Missing.")
return false
}
@ -50,12 +51,12 @@ public extension LMAssembly {
let dict = try FileManager.default.attributesOfItem(atPath: path)
if let value = dict[FileAttributeKey.size] as? UInt64 { fileSize = value }
} catch {
vCLMLog("EOF Fix Failed: File Missing at \(path).")
vCLog("EOF Fix Failed: File Missing at \(path).")
return false
}
guard let fileSize = fileSize else { return false }
guard let writeFile = FileHandle(forUpdatingAtPath: path) else {
vCLMLog("EOF Fix Failed: File Not Writable at \(path).")
vCLog("EOF Fix Failed: File Not Writable at \(path).")
return false
}
defer { writeFile.closeFile() }
@ -63,11 +64,11 @@ public extension LMAssembly {
/// consolidate()
writeFile.seek(toFileOffset: fileSize - 1)
if writeFile.readDataToEndOfFile().first != 0x0A {
vCLMLog("EOF Missing Confirmed, Start Fixing.")
vCLog("EOF Missing Confirmed, Start Fixing.")
var newData = Data()
newData.append(0x0A)
writeFile.write(newData)
vCLMLog("EOF Successfully Assured.")
vCLog("EOF Successfully Assured.")
}
return false
}
@ -141,29 +142,14 @@ public extension LMAssembly {
// Write consolidated file contents.
try strProcessed.write(to: urlPath, atomically: false, encoding: .utf8)
} catch {
vCLMLog("Consolidation Failed w/ File: \(path), error: \(error)")
vCLog("Consolidation Failed w/ File: \(path), error: \(error)")
return false
}
vCLMLog("Either Consolidation Successful Or No-Need-To-Consolidate.")
vCLog("Either Consolidation Successful Or No-Need-To-Consolidate.")
return true
}
vCLMLog("Consolidation Failed: File Missing at \(path).")
vCLog("Consolidation Failed: File Missing at \(path).")
return false
}
}
}
private extension String {
mutating func regReplace(pattern: String, replaceWith: String = "") {
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
do {
let regex = try NSRegularExpression(
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]
)
let range = NSRange(startIndex..., in: self)
self = regex.stringByReplacingMatches(
in: self, options: [], range: range, withTemplate: replaceWith
)
} catch { return }
}
}

View File

@ -8,8 +8,10 @@
import Foundation
import Megrez
import Shared
import SQLite3
public extension LMAssembly {
public extension vChewingLM {
/// LMInstantiatorLMI
/// LangModelProtocol 使
///
@ -28,49 +30,41 @@ public extension LMAssembly {
/// LMI LMI
///
class LMInstantiator: LangModelProtocol {
public struct Config {
/// nil
/// true = false =
public var numPadFWHWStatus: Bool?
public var isCassetteEnabled = false
public var isPhraseReplacementEnabled = false
public var isCNSEnabled = false
public var isSymbolEnabled = false
public var isSCPCEnabled = false
public var filterNonCNSReadings = false
public var deltaOfCalendarYears: Int = -2000
}
public static var asyncLoadingUserData: Bool = true
// SQLite
static var ptrSQL: OpaquePointer?
// SQLite
public internal(set) static var isSQLDBConnected: Bool = false
//
public let isCHS: Bool
public private(set) static var isSQLDBConnected: Bool = false
//
public private(set) var config = Config()
public var isCassetteEnabled = false
public var isPhraseReplacementEnabled = false
public var isCNSEnabled = false
public var isSymbolEnabled = false
public var isSCPCEnabled = false
public var isCHS = false
public var deltaOfCalendarYears: Int = -2000
// package
public init(
isCHS: Bool = false,
uomDataURL: URL? = nil
) {
public init(isCHS: Bool = false) {
self.isCHS = isCHS
lmUserOverride = .init(dataURL: uomDataURL)
}
@discardableResult public func setOptions(handler: (inout Config) -> Void) -> LMInstantiator {
handler(&config)
return self
@discardableResult public static func connectSQLDB(dbPath: String, dropPreviousConnection: Bool = true) -> Bool {
if dropPreviousConnection { disconnectSQLDB() }
vCLog("Establishing SQLite connection to: \(dbPath)")
guard sqlite3_open(dbPath, &Self.ptrSQL) == SQLITE_OK else { return false }
guard "PRAGMA journal_mode = MEMORY;".runAsSQLExec(dbPointer: &ptrSQL) else { return false }
isSQLDBConnected = true
return true
}
public static func setCassetCandidateKeyValidator(_ validator: @escaping (String) -> Bool) {
Self.lmCassette.candidateKeysValidator = validator
public static func disconnectSQLDB() {
if Self.ptrSQL != nil {
sqlite3_close_v2(Self.ptrSQL)
Self.ptrSQL = nil
}
isSQLDBConnected = false
}
///
@ -79,13 +73,12 @@ public extension LMAssembly {
///
/// LMCoreEX Unigram
/// LMCoreEX 滿
/// LMReplacements LMAssociates 使
/// LMReplacements LMAssociates 使
/// LMCoreEX 2010-2013 mac
/// LMCoreJSON JSON
// currentCassetteMetadata
static var lmCassette = LMCassette()
static var lmPlainBopomofo = LMPlainBopomofo()
// 使
// 使使
@ -100,46 +93,30 @@ public extension LMAssembly {
)
var lmReplacements = LMReplacements()
var lmAssociates = LMAssociates()
//
var lmUserOverride: LMUserOverride
var lmPlainBopomofo = LMPlainBopomofo()
// MARK: -
public func resetFactoryJSONModels() {}
public func loadUserPhrasesData(path: String, filterPath: String?) {
func loadMain() {
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: path) {
lmUserPhrases.clear()
lmUserPhrases.open(path)
vCLMLog("lmUserPhrases: \(lmUserPhrases.count) entries of data loaded from: \(path)")
self.lmUserPhrases.clear()
self.lmUserPhrases.open(path)
vCLog("lmUserPhrases: \(self.lmUserPhrases.count) entries of data loaded from: \(path)")
} else {
vCLMLog("lmUserPhrases: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
loadMain()
} else {
DispatchQueue.main.async {
loadMain()
vCLog("lmUserPhrases: File access failure: \(path)")
}
}
guard let filterPath = filterPath else { return }
func loadFilter() {
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: filterPath) {
lmFiltered.clear()
lmFiltered.open(filterPath)
vCLMLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
self.lmFiltered.clear()
self.lmFiltered.open(filterPath)
vCLog("lmFiltered: \(self.lmFiltered.count) entries of data loaded from: \(path)")
} else {
vCLMLog("lmFiltered: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
loadFilter()
} else {
DispatchQueue.main.async {
loadFilter()
vCLog("lmFiltered: File access failure: \(path)")
}
}
}
@ -149,85 +126,74 @@ public extension LMAssembly {
if FileManager.default.isReadableFile(atPath: path) {
lmFiltered.clear()
lmFiltered.open(path)
vCLMLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
vCLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
} else {
vCLMLog("lmFiltered: File access failure: \(path)")
vCLog("lmFiltered: File access failure: \(path)")
}
}
public func loadUserSymbolData(path: String) {
func load() {
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: path) {
lmUserSymbols.clear()
lmUserSymbols.open(path)
vCLMLog("lmUserSymbol: \(lmUserSymbols.count) entries of data loaded from: \(path)")
self.lmUserSymbols.clear()
self.lmUserSymbols.open(path)
vCLog("lmUserSymbol: \(self.lmUserSymbols.count) entries of data loaded from: \(path)")
} else {
vCLMLog("lmUserSymbol: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
vCLog("lmUserSymbol: File access failure: \(path)")
}
}
}
public func loadUserAssociatesData(path: String) {
func load() {
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: path) {
lmAssociates.clear()
lmAssociates.open(path)
vCLMLog("lmAssociates: \(lmAssociates.count) entries of data loaded from: \(path)")
self.lmAssociates.clear()
self.lmAssociates.open(path)
vCLog("lmAssociates: \(self.lmAssociates.count) entries of data loaded from: \(path)")
} else {
vCLMLog("lmAssociates: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
vCLog("lmAssociates: File access failure: \(path)")
}
}
}
public func loadReplacementsData(path: String) {
func load() {
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: path) {
lmReplacements.clear()
lmReplacements.open(path)
vCLMLog("lmReplacements: \(lmReplacements.count) entries of data loaded from: \(path)")
self.lmReplacements.clear()
self.lmReplacements.open(path)
vCLog("lmReplacements: \(self.lmReplacements.count) entries of data loaded from: \(path)")
} else {
vCLMLog("lmReplacements: File access failure: \(path)")
vCLog("lmReplacements: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
}
public func loadSCPCSequencesData() {
let fileName = !isCHS ? "sequenceDataFromEtenDOS-cht" : "sequenceDataFromEtenDOS-chs"
guard let path = Bundle.module.path(forResource: fileName, ofType: "json") else {
vCLog("lmPlainBopomofo: File name access failure: \(fileName)")
return
}
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: path) {
self.lmPlainBopomofo.clear()
self.lmPlainBopomofo.open(path)
vCLog("lmPlainBopomofo: \(self.lmPlainBopomofo.count) entries of data loaded from: \(path)")
} else {
vCLog("lmPlainBopomofo: File access failure: \(path)")
}
}
}
public var isCassetteDataLoaded: Bool { Self.lmCassette.isLoaded }
public static func loadCassetteData(path: String) {
func load() {
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: path) {
Self.lmCassette.clear()
Self.lmCassette.open(path)
vCLMLog("lmCassette: \(Self.lmCassette.count) entries of data loaded from: \(path)")
vCLog("lmCassette: \(Self.lmCassette.count) entries of data loaded from: \(path)")
} else {
vCLMLog("lmCassette: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
vCLog("lmCassette: File access failure: \(path)")
}
}
}
@ -349,57 +315,33 @@ public extension LMAssembly {
///
var rawAllUnigrams: [Megrez.Unigram] = []
if config.isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: keyChain) }
if isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: keyChain) }
// 使
if config.isSCPCEnabled {
rawAllUnigrams += Self.lmPlainBopomofo.valuesFor(key: keyChain, isCHS: isCHS).map {
Megrez.Unigram(value: $0, score: 0)
}
}
if !config.isCassetteEnabled || config.isCassetteEnabled && keyChain.map(\.description)[0] == "_" {
// NumPad
rawAllUnigrams += supplyNumPadUnigrams(key: keyChain)
// LMMisc LMCore score (-10.0, 0.0)
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCHEW)
//
var coreUnigramsResult: [Megrez.Unigram] = factoryCoreUnigramsFor(key: keyChain)
// CNS11643
if config.filterNonCNSReadings, !isCHS {
coreUnigramsResult.removeAll { thisUnigram in
!checkCNSConformation(for: thisUnigram, keyArray: keyArray)
}
}
//
rawAllUnigrams += coreUnigramsResult
if config.isCNSEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCNS)
}
}
if config.isSymbolEnabled {
rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain)
if !config.isCassetteEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataSYMB)
}
if isSCPCEnabled {
rawAllUnigrams += lmPlainBopomofo.valuesFor(key: keyChain).map { Megrez.Unigram(value: $0, score: 0) }
}
// reversed 使
//
// rawUserUnigrams
var userPhraseUnigrams = Array(lmUserPhrases.unigramsFor(key: keyChain).reversed())
if keyArray.count == 1, let topScore = rawAllUnigrams.map(\.score).max() {
// 使
userPhraseUnigrams = userPhraseUnigrams.map { currentUnigram in
Megrez.Unigram(
value: currentUnigram.value,
score: Swift.min(topScore + 0.000_114_514, currentUnigram.score)
)
rawAllUnigrams += lmUserPhrases.unigramsFor(key: keyChain).reversed()
if !isCassetteEnabled || isCassetteEnabled && keyChain.map(\.description)[0] == "_" {
// LMMisc LMCore score (-10.0, 0.0)
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCHEW)
rawAllUnigrams += factoryCoreUnigramsFor(key: keyChain)
if isCNSEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCNS)
}
}
if isSymbolEnabled {
rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain)
if !isCassetteEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataSYMB)
}
}
rawAllUnigrams = userPhraseUnigrams + rawAllUnigrams
// InputToken
rawAllUnigrams = rawAllUnigrams.map { unigram in
@ -421,7 +363,7 @@ public extension LMAssembly {
}
//
if config.isPhraseReplacementEnabled {
if isPhraseReplacementEnabled {
for i in 0 ..< rawAllUnigrams.count {
let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value)
guard !newValue.isEmpty else { continue }

View File

@ -8,9 +8,9 @@
import Foundation
import Megrez
import SwiftExtension
import Shared
public extension LMAssembly.LMInstantiator {
public extension vChewingLM.LMInstantiator {
///
var cassetteWildcardKey: String { Self.lmCassette.wildcardKey }
///

View File

@ -11,7 +11,7 @@ import Megrez
// MARK: - 便
extension LMAssembly.LMInstantiator {
extension vChewingLM.LMInstantiator {
func queryDateTimeUnigrams(with key: String = "") -> [Megrez.Unigram] {
guard let tokenTrigger = TokenTrigger(rawValue: key) else { return [] }
var results = [Megrez.Unigram]()
@ -19,14 +19,14 @@ extension LMAssembly.LMInstantiator {
func processDateWithDayDelta(_ delta: Int) {
tokens = ["MACRO@DATE_DAYDELTA:\(delta)"]
if config.deltaOfCalendarYears != 0 { tokens.append("MACRO@DATE_DAYDELTA:\(delta)_YEARDELTA:\(config.deltaOfCalendarYears)") }
if deltaOfCalendarYears != 0 { tokens.append("MACRO@DATE_DAYDELTA:\(delta)_YEARDELTA:\(deltaOfCalendarYears)") }
tokens.append("MACRO@DATE_DAYDELTA:\(delta)_SHORTENED")
tokens.append("MACRO@DATE_DAYDELTA:\(delta)_LUNA")
}
func processYearWithYearDelta(_ delta: Int) {
tokens = ["MACRO@YEAR_YEARDELTA:\(delta)"]
if config.deltaOfCalendarYears != 0 { tokens.append("MACRO@YEAR_YEARDELTA:\(delta + config.deltaOfCalendarYears)") }
if deltaOfCalendarYears != 0 { tokens.append("MACRO@YEAR_YEARDELTA:\(delta + deltaOfCalendarYears)") }
tokens.append("MACRO@YEAR_GANZHI_YEARDELTA:\(delta)")
tokens.append("MACRO@YEAR_ZODIAC_YEARDELTA:\(delta)")
}

View File

@ -1,23 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
public extension LMAssembly.LMInstantiator {
func supplyNumPadUnigrams(key: String) -> [Megrez.Unigram] {
guard let status = config.numPadFWHWStatus else { return [] }
let initials = "_NumPad_"
guard key.hasPrefix(initials) else { return [] }
let char = key.replacingOccurrences(of: initials, with: "")
guard char.count == 1 else { return [] }
let gram1 = Megrez.Unigram(value: char.applyingTransformFW2HW(reverse: status), score: 0)
let gram2 = Megrez.Unigram(value: char.applyingTransformFW2HW(reverse: !status), score: -0.1)
return [gram1, gram2]
}
}

View File

@ -8,6 +8,7 @@
import Foundation
import Megrez
import Shared
import SQLite3
/* ==============
@ -30,49 +31,30 @@ import SQLite3
) WITHOUT ROWID;
*/
extension LMAssembly.LMInstantiator {
enum CoreColumn: Int32 {
case theDataCHS = 1 //
case theDataCHT = 2 //
case theDataCNS = 3 //
case theDataMISC = 4 //
case theDataSYMB = 5 //
case theDataCHEW = 6 //
enum CoreColumn: Int32 {
case theDataCHS = 1 //
case theDataCHT = 2 //
case theDataCNS = 3 //
case theDataMISC = 4 //
case theDataSYMB = 5 //
case theDataCHEW = 6 //
var name: String { String(describing: self) }
var name: String { String(describing: self) }
var id: Int32 { rawValue }
var id: Int32 { rawValue }
var defaultScore: Double {
switch self {
case .theDataCHEW: return -1
case .theDataCNS: return -11
case .theDataSYMB: return -13
case .theDataMISC: return -10
default: return -9.9
}
var defaultScore: Double {
switch self {
case .theDataCHEW: return -1
case .theDataCNS: return -11
case .theDataSYMB: return -13
case .theDataMISC: return -10
default: return -9.9
}
}
}
extension LMAssembly.LMInstantiator {
@discardableResult public static func connectSQLDB(dbPath: String, dropPreviousConnection: Bool = true) -> Bool {
if dropPreviousConnection { disconnectSQLDB() }
vCLMLog("Establishing SQLite connection to: \(dbPath)")
guard sqlite3_open(dbPath, &Self.ptrSQL) == SQLITE_OK else { return false }
guard "PRAGMA journal_mode = OFF;".runAsSQLExec(dbPointer: &ptrSQL) else { return false }
isSQLDBConnected = true
return true
}
public static func disconnectSQLDB() {
if Self.ptrSQL != nil {
sqlite3_close_v2(Self.ptrSQL)
Self.ptrSQL = nil
}
isSQLDBConnected = false
}
extension vChewingLM.LMInstantiator {
fileprivate static func querySQL(strStmt sqlQuery: String, coreColumn column: CoreColumn, handler: (String) -> Void) {
guard Self.ptrSQL != nil else { return }
performStatementSansResult { ptrStatement in
@ -141,10 +123,9 @@ extension LMAssembly.LMInstantiator {
}
/// UTF8
/// - Remark: 使
/// - parameters:
/// - key:
public func factoryCoreUnigramsFor(key: String) -> [Megrez.Unigram] {
func factoryCoreUnigramsFor(key: String) -> [Megrez.Unigram] {
// ASCII SQLite
factoryUnigramsFor(key: key, column: isCHS ? .theDataCHS : .theDataCHT)
}
@ -153,9 +134,7 @@ extension LMAssembly.LMInstantiator {
/// - parameters:
/// - key:
/// - column:
func factoryUnigramsFor(
key: String, column: LMAssembly.LMInstantiator.CoreColumn
) -> [Megrez.Unigram] {
func factoryUnigramsFor(key: String, column: CoreColumn) -> [Megrez.Unigram] {
if key == "_punctuation_list" { return [] }
var grams: [Megrez.Unigram] = []
var gramsHW: [Megrez.Unigram] = []
@ -163,10 +142,8 @@ extension LMAssembly.LMInstantiator {
let encryptedKey = Self.cnvPhonabetToASCII(key.replacingOccurrences(of: "'", with: "''"))
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)';"
Self.querySQL(strStmt: sqlQuery, coreColumn: column) { currentResult in
var i: Double = 0
var previousScore: Double?
currentResult.split(separator: "\t").forEach { strNetaSet in
// stable sort
let arrRangeRecords = currentResult.split(separator: "\t")
for strNetaSet in arrRangeRecords {
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
let theValue: String = .init(neta[0])
var theScore = column.defaultScore
@ -176,15 +153,8 @@ extension LMAssembly.LMInstantiator {
if theScore > 0 {
theScore *= -1 //
}
if previousScore == theScore {
theScore -= i * 0.000_001
i += 1
} else {
previousScore = theScore
i = 0
}
grams.append(Megrez.Unigram(value: theValue, score: theScore))
if !key.contains("_punctuation") { return }
if !key.contains("_punctuation") { continue }
let halfValue = theValue.applyingTransformFW2HW(reverse: false)
if halfValue != theValue {
gramsHW.append(Megrez.Unigram(value: halfValue, score: theScore))
@ -195,24 +165,6 @@ extension LMAssembly.LMInstantiator {
return grams
}
/// CNS UTF8
/// CNS
/// - parameters:
/// - key:
/// - column:
private func factoryCNSFilterThreadFor(key: String) -> String? {
let column = CoreColumn.theDataCNS
if key == "_punctuation_list" { return nil }
var results: [String] = []
// ASCII SQLite
let encryptedKey = Self.cnvPhonabetToASCII(key.replacingOccurrences(of: "'", with: "''"))
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)';"
Self.querySQL(strStmt: sqlQuery, coreColumn: column) { currentResult in
results.append(currentResult)
}
return results.joined(separator: "\t")
}
/// UTF8
/// - remark:
/// - parameters:
@ -225,22 +177,9 @@ extension LMAssembly.LMInstantiator {
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)' AND \(column.name) IS NOT NULL"
return Self.hasSQLResult(strStmt: sqlQuery)
}
/// Unigram CNS11643
/// 使
func checkCNSConformation(for unigram: Megrez.Unigram, keyArray: [String]) -> Bool {
guard unigram.value.count == keyArray.count else { return true }
let chars = unigram.value.map(\.description)
for (i, key) in keyArray.enumerated() {
guard !key.hasPrefix("_") else { continue }
guard let matchedCNSResult = factoryCNSFilterThreadFor(key: key) else { continue }
guard matchedCNSResult.contains(chars[i]) else { return false }
}
return true
}
}
private extension LMAssembly.LMInstantiator {
private extension vChewingLM.LMInstantiator {
///
///
/// 使 json
@ -288,7 +227,7 @@ private extension LMAssembly.LMInstantiator {
]
}
public extension LMAssembly.LMInstantiator {
public extension vChewingLM.LMInstantiator {
@discardableResult static func connectToTestSQLDB() -> Bool {
Self.connectSQLDB(dbPath: #":memory:"#) && sqlTestCoreLMData.runAsSQLExec(dbPointer: &ptrSQL)
}

View File

@ -1,60 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
public extension LMAssembly.LMInstantiator {
func performUOMObservation(
walkedBefore: [Megrez.Node],
walkedAfter: [Megrez.Node],
cursor: Int,
timestamp: Double,
saveCallback: (() -> Void)? = nil
) {
lmUserOverride.performObservation(
walkedBefore: walkedBefore,
walkedAfter: walkedAfter,
cursor: cursor,
timestamp: timestamp,
saveCallback: saveCallback
)
}
func fetchUOMSuggestion(
currentWalk: [Megrez.Node],
cursor: Int,
timestamp: Double
) -> LMAssembly.OverrideSuggestion {
lmUserOverride.fetchSuggestion(
currentWalk: currentWalk,
cursor: cursor,
timestamp: timestamp
)
}
func loadUOMData(fromURL fileURL: URL? = nil) {
lmUserOverride.loadData(fromURL: fileURL)
}
func saveUOMData(toURL fileURL: URL? = nil) {
lmUserOverride.saveData(toURL: fileURL)
}
func clearUOMData(withURL fileURL: URL? = nil) {
lmUserOverride.clearData(withURL: fileURL)
}
func bleachSpecifiedUOMSuggestions(targets: [String], saveCallback: (() -> Void)? = nil) {
lmUserOverride.bleachSpecifiedSuggestions(targets: targets, saveCallback: saveCallback)
}
func bleachUOMUnigrams(saveCallback: (() -> Void)? = nil) {
lmUserOverride.bleachUnigrams(saveCallback: saveCallback)
}
}

View File

@ -1,33 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
public extension LMAssembly {
struct UserDictionarySummarized: Codable {
let isCHS: Bool
let userPhrases: [String: [String]]
let filter: [String: [String]]
let userSymbols: [String: [String]]
let replacements: [String: String]
let associates: [String: [String]]
}
}
public extension LMAssembly.LMInstantiator {
func summarize(all: Bool) -> LMAssembly.UserDictionarySummarized {
LMAssembly.UserDictionarySummarized(
isCHS: isCHS,
userPhrases: lmUserPhrases.dictRepresented,
filter: lmFiltered.dictRepresented,
userSymbols: lmUserSymbols.dictRepresented,
replacements: lmReplacements.dictRepresented,
associates: all ? lmAssociates.dictRepresented : [:]
)
}
}

View File

@ -1,123 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
// 使 LMAssembly Tekkon
private typealias LengthSortedDictionary = [Int: [String: String]]
private let mapHanyuPinyinToPhonabets: LengthSortedDictionary = {
let parsed = try? JSONDecoder().decode(LengthSortedDictionary.self, from: jsnHanyuPinyinToMPS.data(using: .utf8) ?? Data([]))
return parsed ?? [:]
}()
extension String {
mutating func convertToPhonabets(newToneOne: String = "") {
if isEmpty || contains("_") || !isNotPureAlphanumerical { return }
let lengths = mapHanyuPinyinToPhonabets.keys.sorted().reversed()
lengths.forEach { length in
mapHanyuPinyinToPhonabets[length]?.forEach { key, value in
self = replacingOccurrences(of: key, with: value)
}
}
self = replacingOccurrences(of: " ", with: newToneOne)
}
}
///
private extension String {
var isNotPureAlphanumerical: Bool {
let regex = ".*[^A-Za-z0-9].*"
let testString = NSPredicate(format: "SELF MATCHES %@", regex)
return testString.evaluate(with: self)
}
}
private let jsnHanyuPinyinToMPS = #"""
{
"1":{"1":" ","2":"ˊ","3":"ˇ","4":"ˋ","5":"˙","a":"","e":"","o":"","q":""},
"2":{"ai":"","an":"","ao":"","ba":"ㄅㄚ","bi":"ㄅㄧ","bo":"ㄅㄛ","bu":"ㄅㄨ",
"ca":"ㄘㄚ","ce":"ㄘㄜ","ci":"","cu":"ㄘㄨ","da":"ㄉㄚ","de":"ㄉㄜ","di":"ㄉㄧ",
"du":"ㄉㄨ","eh":"","ei":"","en":"","er":"","fa":"ㄈㄚ","fo":"ㄈㄛ",
"fu":"ㄈㄨ","ga":"ㄍㄚ","ge":"ㄍㄜ","gi":"ㄍㄧ","gu":"ㄍㄨ","ha":"ㄏㄚ","he":"ㄏㄜ",
"hu":"ㄏㄨ","ji":"ㄐㄧ","ju":"ㄐㄩ","ka":"ㄎㄚ","ke":"ㄎㄜ","ku":"ㄎㄨ","la":"ㄌㄚ",
"le":"ㄌㄜ","li":"ㄌㄧ","lo":"ㄌㄛ","lu":"ㄌㄨ","lv":"ㄌㄩ","ma":"ㄇㄚ","me":"ㄇㄜ",
"mi":"ㄇㄧ","mo":"ㄇㄛ","mu":"ㄇㄨ","na":"ㄋㄚ","ne":"ㄋㄜ","ni":"ㄋㄧ","nu":"ㄋㄨ",
"nv":"ㄋㄩ","ou":"","pa":"ㄆㄚ","pi":"ㄆㄧ","po":"ㄆㄛ","pu":"ㄆㄨ","qi":"ㄑㄧ",
"qu":"ㄑㄩ","re":"ㄖㄜ","ri":"","ru":"ㄖㄨ","sa":"ㄙㄚ","se":"ㄙㄜ","si":"",
"su":"ㄙㄨ","ta":"ㄊㄚ","te":"ㄊㄜ","ti":"ㄊㄧ","tu":"ㄊㄨ","wa":"ㄨㄚ","wo":"ㄨㄛ",
"wu":"","xi":"ㄒㄧ","xu":"ㄒㄩ","ya":"ㄧㄚ","ye":"ㄧㄝ","yi":"","yo":"ㄧㄛ",
"yu":"","za":"ㄗㄚ","ze":"ㄗㄜ","zi":"","zu":"ㄗㄨ"},
"3":{"ang":"","bai":"ㄅㄞ","ban":"ㄅㄢ","bao":"ㄅㄠ","bei":"ㄅㄟ","ben":"ㄅㄣ",
"bie":"ㄅㄧㄝ","bin":"ㄅㄧㄣ","cai":"ㄘㄞ","can":"ㄘㄢ","cao":"ㄘㄠ","cei":"ㄘㄟ",
"cen":"ㄘㄣ","cha":"ㄔㄚ","che":"ㄔㄜ","chi":"","chu":"ㄔㄨ","cou":"ㄘㄡ",
"cui":"ㄘㄨㄟ","cun":"ㄘㄨㄣ","cuo":"ㄘㄨㄛ","dai":"ㄉㄞ","dan":"ㄉㄢ","dao":"ㄉㄠ",
"dei":"ㄉㄟ","den":"ㄉㄣ","dia":"ㄉㄧㄚ","die":"ㄉㄧㄝ","diu":"ㄉㄧㄡ","dou":"ㄉㄡ",
"dui":"ㄉㄨㄟ","dun":"ㄉㄨㄣ","duo":"ㄉㄨㄛ","eng":"","fan":"ㄈㄢ","fei":"ㄈㄟ",
"fen":"ㄈㄣ","fou":"ㄈㄡ","gai":"ㄍㄞ","gan":"ㄍㄢ","gao":"ㄍㄠ","gei":"ㄍㄟ",
"gen":"ㄍㄣ","gin":"ㄍㄧㄣ","gou":"ㄍㄡ","gua":"ㄍㄨㄚ","gue":"ㄍㄨㄜ","gui":"ㄍㄨㄟ",
"gun":"ㄍㄨㄣ","guo":"ㄍㄨㄛ","hai":"ㄏㄞ","han":"ㄏㄢ","hao":"ㄏㄠ","hei":"ㄏㄟ",
"hen":"ㄏㄣ","hou":"ㄏㄡ","hua":"ㄏㄨㄚ","hui":"ㄏㄨㄟ","hun":"ㄏㄨㄣ","huo":"ㄏㄨㄛ",
"jia":"ㄐㄧㄚ","jie":"ㄐㄧㄝ","jin":"ㄐㄧㄣ","jiu":"ㄐㄧㄡ","jue":"ㄐㄩㄝ",
"jun":"ㄐㄩㄣ","kai":"ㄎㄞ","kan":"ㄎㄢ","kao":"ㄎㄠ","ken":"ㄎㄣ","kiu":"ㄎㄧㄡ",
"kou":"ㄎㄡ","kua":"ㄎㄨㄚ","kui":"ㄎㄨㄟ","kun":"ㄎㄨㄣ","kuo":"ㄎㄨㄛ","lai":"ㄌㄞ",
"lan":"ㄌㄢ","lao":"ㄌㄠ","lei":"ㄌㄟ","lia":"ㄌㄧㄚ","lie":"ㄌㄧㄝ","lin":"ㄌㄧㄣ",
"liu":"ㄌㄧㄡ","lou":"ㄌㄡ","lun":"ㄌㄨㄣ","luo":"ㄌㄨㄛ","lve":"ㄌㄩㄝ","mai":"ㄇㄞ",
"man":"ㄇㄢ","mao":"ㄇㄠ","mei":"ㄇㄟ","men":"ㄇㄣ","mie":"ㄇㄧㄝ","min":"ㄇㄧㄣ",
"miu":"ㄇㄧㄡ","mou":"ㄇㄡ","nai":"ㄋㄞ","nan":"ㄋㄢ","nao":"ㄋㄠ","nei":"ㄋㄟ",
"nen":"ㄋㄣ","nie":"ㄋㄧㄝ","nin":"ㄋㄧㄣ","niu":"ㄋㄧㄡ","nou":"ㄋㄡ","nui":"ㄋㄨㄟ",
"nun":"ㄋㄨㄣ","nuo":"ㄋㄨㄛ","nve":"ㄋㄩㄝ","pai":"ㄆㄞ","pan":"ㄆㄢ","pao":"ㄆㄠ",
"pei":"ㄆㄟ","pen":"ㄆㄣ","pia":"ㄆㄧㄚ","pie":"ㄆㄧㄝ","pin":"ㄆㄧㄣ","pou":"ㄆㄡ",
"qia":"ㄑㄧㄚ","qie":"ㄑㄧㄝ","qin":"ㄑㄧㄣ","qiu":"ㄑㄧㄡ","que":"ㄑㄩㄝ",
"qun":"ㄑㄩㄣ","ran":"ㄖㄢ","rao":"ㄖㄠ","ren":"ㄖㄣ","rou":"ㄖㄡ","rui":"ㄖㄨㄟ",
"run":"ㄖㄨㄣ","ruo":"ㄖㄨㄛ","sai":"ㄙㄞ","san":"ㄙㄢ","sao":"ㄙㄠ","sei":"ㄙㄟ",
"sen":"ㄙㄣ","sha":"ㄕㄚ","she":"ㄕㄜ","shi":"","shu":"ㄕㄨ","sou":"ㄙㄡ",
"sui":"ㄙㄨㄟ","sun":"ㄙㄨㄣ","suo":"ㄙㄨㄛ","tai":"ㄊㄞ","tan":"ㄊㄢ","tao":"ㄊㄠ",
"tie":"ㄊㄧㄝ","tou":"ㄊㄡ","tui":"ㄊㄨㄟ","tun":"ㄊㄨㄣ","tuo":"ㄊㄨㄛ",
"wai":"ㄨㄞ","wan":"ㄨㄢ","wei":"ㄨㄟ","wen":"ㄨㄣ","xia":"ㄒㄧㄚ","xie":"ㄒㄧㄝ",
"xin":"ㄒㄧㄣ","xiu":"ㄒㄧㄡ","xue":"ㄒㄩㄝ","xun":"ㄒㄩㄣ","yai":"ㄧㄞ",
"yan":"ㄧㄢ","yao":"ㄧㄠ","yin":"ㄧㄣ","you":"ㄧㄡ","yue":"ㄩㄝ","yun":"ㄩㄣ",
"zai":"ㄗㄞ","zan":"ㄗㄢ","zao":"ㄗㄠ","zei":"ㄗㄟ","zen":"ㄗㄣ","zha":"ㄓㄚ",
"zhe":"ㄓㄜ","zhi":"","zhu":"ㄓㄨ","zou":"ㄗㄡ","zui":"ㄗㄨㄟ","zun":"ㄗㄨㄣ",
"zuo":"ㄗㄨㄛ"},
"4":{"bang":"ㄅㄤ","beng":"ㄅㄥ","bian":"ㄅㄧㄢ","biao":"ㄅㄧㄠ","bing":"ㄅㄧㄥ",
"cang":"ㄘㄤ","ceng":"ㄘㄥ","chai":"ㄔㄞ","chan":"ㄔㄢ","chao":"ㄔㄠ","chen":"ㄔㄣ",
"chou":"ㄔㄡ","chua":"ㄔㄨㄚ","chui":"ㄔㄨㄟ","chun":"ㄔㄨㄣ","chuo":"ㄔㄨㄛ",
"cong":"ㄘㄨㄥ","cuan":"ㄘㄨㄢ","dang":"ㄉㄤ","deng":"ㄉㄥ","dian":"ㄉㄧㄢ",
"diao":"ㄉㄧㄠ","ding":"ㄉㄧㄥ","dong":"ㄉㄨㄥ","duan":"ㄉㄨㄢ","fang":"ㄈㄤ",
"feng":"ㄈㄥ","fiao":"ㄈㄧㄠ","fong":"ㄈㄨㄥ","gang":"ㄍㄤ","geng":"ㄍㄥ",
"giao":"ㄍㄧㄠ","gong":"ㄍㄨㄥ","guai":"ㄍㄨㄞ","guan":"ㄍㄨㄢ","hang":"ㄏㄤ",
"heng":"ㄏㄥ","hong":"ㄏㄨㄥ","huai":"ㄏㄨㄞ","huan":"ㄏㄨㄢ","jian":"ㄐㄧㄢ",
"jiao":"ㄐㄧㄠ","jing":"ㄐㄧㄥ","juan":"ㄐㄩㄢ","kang":"ㄎㄤ","keng":"ㄎㄥ",
"kong":"ㄎㄨㄥ","kuai":"ㄎㄨㄞ","kuan":"ㄎㄨㄢ","lang":"ㄌㄤ","leng":"ㄌㄥ",
"lian":"ㄌㄧㄢ","liao":"ㄌㄧㄠ","ling":"ㄌㄧㄥ","long":"ㄌㄨㄥ","luan":"ㄌㄨㄢ",
"lvan":"ㄌㄩㄢ","mang":"ㄇㄤ","meng":"ㄇㄥ","mian":"ㄇㄧㄢ","miao":"ㄇㄧㄠ",
"ming":"ㄇㄧㄥ","nang":"ㄋㄤ","neng":"ㄋㄥ","nian":"ㄋㄧㄢ","niao":"ㄋㄧㄠ",
"ning":"ㄋㄧㄥ","nong":"ㄋㄨㄥ","nuan":"ㄋㄨㄢ","pang":"ㄆㄤ","peng":"ㄆㄥ",
"pian":"ㄆㄧㄢ","piao":"ㄆㄧㄠ","ping":"ㄆㄧㄥ","qian":"ㄑㄧㄢ","qiao":"ㄑㄧㄠ",
"qing":"ㄑㄧㄥ","quan":"ㄑㄩㄢ","rang":"ㄖㄤ","reng":"ㄖㄥ","rong":"ㄖㄨㄥ",
"ruan":"ㄖㄨㄢ","sang":"ㄙㄤ","seng":"ㄙㄥ","shai":"ㄕㄞ","shan":"ㄕㄢ",
"shao":"ㄕㄠ","shei":"ㄕㄟ","shen":"ㄕㄣ","shou":"ㄕㄡ","shua":"ㄕㄨㄚ",
"shui":"ㄕㄨㄟ","shun":"ㄕㄨㄣ","shuo":"ㄕㄨㄛ","song":"ㄙㄨㄥ","suan":"ㄙㄨㄢ",
"tang":"ㄊㄤ","teng":"ㄊㄥ","tian":"ㄊㄧㄢ","tiao":"ㄊㄧㄠ","ting":"ㄊㄧㄥ",
"tong":"ㄊㄨㄥ","tuan":"ㄊㄨㄢ","wang":"ㄨㄤ","weng":"ㄨㄥ","xian":"ㄒㄧㄢ",
"xiao":"ㄒㄧㄠ","xing":"ㄒㄧㄥ","xuan":"ㄒㄩㄢ","yang":"ㄧㄤ","ying":"ㄧㄥ",
"yong":"ㄩㄥ","yuan":"ㄩㄢ","zang":"ㄗㄤ","zeng":"ㄗㄥ","zhai":"ㄓㄞ",
"zhan":"ㄓㄢ","zhao":"ㄓㄠ","zhei":"ㄓㄟ","zhen":"ㄓㄣ","zhou":"ㄓㄡ",
"zhua":"ㄓㄨㄚ","zhui":"ㄓㄨㄟ","zhun":"ㄓㄨㄣ","zhuo":"ㄓㄨㄛ",
"zong":"ㄗㄨㄥ","zuan":"ㄗㄨㄢ"},
"5":{"biang":"ㄅㄧㄤ","chang":"ㄔㄤ","cheng":"ㄔㄥ","chong":"ㄔㄨㄥ","chuai":"ㄔㄨㄞ",
"chuan":"ㄔㄨㄢ","duang":"ㄉㄨㄤ","guang":"ㄍㄨㄤ","huang":"ㄏㄨㄤ","jiang":"ㄐㄧㄤ",
"jiong":"ㄐㄩㄥ","kiang":"ㄎㄧㄤ","kuang":"ㄎㄨㄤ","liang":"ㄌㄧㄤ","niang":"ㄋㄧㄤ",
"qiang":"ㄑㄧㄤ","qiong":"ㄑㄩㄥ","shang":"ㄕㄤ","sheng":"ㄕㄥ","shuai":"ㄕㄨㄞ",
"shuan":"ㄕㄨㄢ","xiang":"ㄒㄧㄤ","xiong":"ㄒㄩㄥ","zhang":"ㄓㄤ","zheng":"ㄓㄥ",
"zhong":"ㄓㄨㄥ","zhuai":"ㄓㄨㄞ","zhuan":"ㄓㄨㄢ"},
"6":{"chuang":"ㄔㄨㄤ","shuang":"ㄕㄨㄤ","zhuang":"ㄓㄨㄤ"}
}
"""#

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,11 +7,13 @@
// requirements defined in MIT License.
import Megrez
import PinyinPhonaConverter
import Shared
extension LMAssembly {
struct LMAssociates {
public extension vChewingLM {
@frozen struct LMAssociates {
public private(set) var filePath: String?
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:] // Range index
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
var strData: String = ""
public var count: Int { rangeMap.count }
@ -46,8 +48,8 @@ extension LMAssembly {
replaceData(textData: rawStrData)
} catch {
filePath = oldPath
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when reading data at: \(path).")
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -91,21 +93,28 @@ extension LMAssembly {
do {
try strData.write(toFile: filePath, atomically: true, encoding: .utf8)
} catch {
vCLMLog("Failed to save current database to: \(filePath)")
vCLog("Failed to save current database to: \(filePath)")
}
}
public func valuesFor(pair: Megrez.KeyValuePaired) -> [String] {
var pairs: [String] = []
let availableResults = [rangeMap[pair.toNGramKey], rangeMap[pair.value]].compactMap { $0 }
availableResults.forEach { arrRangeRecords in
arrRangeRecords.forEach { netaRange, index in
if let arrRangeRecords: [(Range<String.Index>, Int)] = rangeMap[pair.toNGramKey] {
for (netaRange, index) in arrRangeRecords {
let neta = strData[netaRange].split(separator: " ")
let theValue: String = .init(neta[index])
pairs.append(theValue)
}
}
return pairs.deduplicated
if let arrRangeRecords: [(Range<String.Index>, Int)] = rangeMap[pair.value] {
for (netaRange, index) in arrRangeRecords {
let neta = strData[netaRange].split(separator: " ")
let theValue: String = .init(neta[index])
pairs.append(theValue)
}
}
var set = Set<String>()
return pairs.filter { set.insert($0).inserted }
}
public func hasValuesFor(pair: Megrez.KeyValuePaired) -> Bool {
@ -114,17 +123,3 @@ extension LMAssembly {
}
}
}
extension LMAssembly.LMAssociates {
var dictRepresented: [String: [String]] {
var result = [String: [String]]()
rangeMap.forEach { key, arrRangeRecords in
arrRangeRecords.forEach { netaRange, index in
let neta = strData[netaRange].split(separator: " ")
let theValue: String = .init(neta[index])
result[key, default: []].append(theValue)
}
}
return result
}
}

View File

@ -10,10 +10,11 @@
import Foundation
import LineReader
import Megrez
import Shared
extension LMAssembly {
public extension vChewingLM {
/// 便使
struct LMCassette {
@frozen struct LMCassette {
public private(set) var filePath: String?
public private(set) var nameShort: String = ""
public private(set) var nameENG: String = ""
@ -39,13 +40,12 @@ extension LMAssembly {
public private(set) var areCandidateKeysShiftHeld: Bool = false
public private(set) var supplyQuickResults: Bool = false
public private(set) var supplyPartiallyMatchedResults: Bool = false
public var candidateKeysValidator: (String) -> Bool = { _ in false }
/// 西 - NORM
private var norm = 0.0
}
}
extension LMAssembly.LMCassette {
public extension vChewingLM.LMCassette {
/// 西 - fscale
private static let fscale = 2.7
///
@ -86,7 +86,7 @@ extension LMAssembly.LMCassette {
if FileManager.default.fileExists(atPath: path) {
do {
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
throw LMAssembly.FileErrors.fileHandleError("")
throw vChewingLM.FileErrors.fileHandleError("")
}
let lineReader = try LineReader(file: fileHandle)
var theMaxKeyLength = 1
@ -193,9 +193,7 @@ extension LMAssembly.LMCassette {
}
}
// Post process.
// Package 便 J / K
//
if !candidateKeysValidator(selectionKeys) { selectionKeys = "1234567890" }
if CandidateKey.validate(keys: selectionKeys) != nil { selectionKeys = "1234567890" }
if !keysUsedInCharDef.intersection(selectionKeys.map(\.description)).isEmpty {
areCandidateKeysShiftHeld = true
}
@ -204,10 +202,10 @@ extension LMAssembly.LMCassette {
filePath = path
return true
} catch {
vCLMLog("CIN Loading Failed: File Access Error.")
vCLog("CIN Loading Failed: File Access Error.")
}
} else {
vCLMLog("CIN Loading Failed: File Missing.")
vCLog("CIN Loading Failed: File Missing.")
}
filePath = oldPath
return false

View File

@ -7,13 +7,15 @@
// requirements defined in MIT License.
import Megrez
import PinyinPhonaConverter
import Shared
extension LMAssembly {
public extension vChewingLM {
/// LMCore LMCoreEX range
/// range strData
/// C++ ParselessLM Swift
/// For
struct LMCoreEX {
@frozen struct LMCoreEX {
public private(set) var filePath: String?
/// 便 strData
var rangeMap: [String: [Range<String.Index>]] = [:]
@ -79,8 +81,8 @@ extension LMAssembly {
replaceData(textData: rawStrData)
} catch {
filePath = oldPath
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when reading data at: \(path).")
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -131,7 +133,7 @@ extension LMAssembly {
}
try dataToWrite.write(toFile: filePath, atomically: true, encoding: .utf8)
} catch {
vCLMLog("Failed to save current database to: \(filePath)")
vCLog("Failed to save current database to: \(filePath)")
}
}
@ -148,7 +150,7 @@ extension LMAssembly {
strDump += addline
}
}
vCLMLog(strDump)
vCLog(strDump)
}
/// strData
@ -184,15 +186,3 @@ extension LMAssembly {
}
}
}
extension LMAssembly.LMCoreEX {
var dictRepresented: [String: [String]] {
var result = [String: [String]]()
rangeMap.forEach { key, arrValueRanges in
result[key, default: []] = arrValueRanges.map { currentRange in
strData[currentRange].description
}
}
return result
}
}

View File

@ -7,36 +7,67 @@
// requirements defined in MIT License.
import Foundation
import Shared
extension LMAssembly {
struct LMPlainBopomofo {
@usableFromInline typealias DataMap = [String: [String: String]]
let dataMap: DataMap
public extension vChewingLM {
@frozen struct LMPlainBopomofo {
public private(set) var filePath: String?
var dataMap: [String: String] = [:]
public var count: Int { dataMap.count }
public init() {
do {
let rawData = jsnEtenDosSequence.data(using: .utf8) ?? .init([])
let rawJSON = try JSONDecoder().decode([String: [String: String]].self, from: rawData)
dataMap = rawJSON
} catch {
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when parsing raw JSON sequence data from vChewing LMAssembly.")
dataMap = [:]
}
dataMap = [:]
}
public var isLoaded: Bool { !dataMap.isEmpty }
public func valuesFor(key: String, isCHS: Bool) -> [String] {
@discardableResult public mutating func open(_ path: String) -> Bool {
if isLoaded { return false }
let oldPath = filePath
filePath = nil
do {
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
if let rawJSON = try? JSONSerialization.jsonObject(with: rawData) as? [String: String] {
dataMap = rawJSON
} else {
filePath = oldPath
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return false
}
} catch {
filePath = oldPath
vCLog("\(error)")
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return false
}
filePath = path
return true
}
public mutating func clear() {
filePath = nil
dataMap.removeAll()
}
public func saveData() {
guard let filePath = filePath, let plistURL = URL(string: filePath) else { return }
do {
let plistData = try PropertyListSerialization.data(fromPropertyList: dataMap, format: .binary, options: 0)
try plistData.write(to: plistURL)
} catch {
vCLog("Failed to save current database to: \(filePath)")
}
}
public func valuesFor(key: String) -> [String] {
var pairs: [String] = []
let subKey = isCHS ? "S" : "T"
if let arrRangeRecords: String = dataMap[key]?[subKey] {
if let arrRangeRecords: String = dataMap[key]?.trimmingCharacters(in: .newlines) {
pairs.append(contentsOf: arrRangeRecords.map(\.description))
}
//
return pairs
return pairs.deduplicated
}
public func hasValuesFor(key: String) -> Bool { dataMap.keys.contains(key) }

View File

@ -6,8 +6,10 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension LMAssembly {
struct LMReplacements {
import Shared
public extension vChewingLM {
@frozen struct LMReplacements {
public private(set) var filePath: String?
var rangeMap: [String: Range<String.Index>] = [:]
var strData: String = ""
@ -33,8 +35,8 @@ extension LMAssembly {
replaceData(textData: rawStrData)
} catch {
filePath = oldPath
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when reading data at: \(path).")
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -70,7 +72,7 @@ extension LMAssembly {
do {
try strData.write(toFile: filePath, atomically: true, encoding: .utf8)
} catch {
vCLMLog("Failed to save current database to: \(filePath)")
vCLog("Failed to save current database to: \(filePath)")
}
}
@ -79,7 +81,7 @@ extension LMAssembly {
for entry in rangeMap {
strDump += strData[entry.value] + "\n"
}
vCLMLog(strDump)
vCLog(strDump)
}
public func valuesFor(key: String) -> String {
@ -98,13 +100,3 @@ extension LMAssembly {
}
}
}
extension LMAssembly.LMReplacements {
var dictRepresented: [String: String] {
var result = [String: String]()
rangeMap.forEach { key, valueRange in
result[key] = strData[valueRange].description
}
return result
}
}

View File

@ -0,0 +1,76 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Shared
public extension vChewingLM {
@frozen struct LMRevLookup {
public private(set) var dataMap: [String: [String]] = [:]
public private(set) var filePath: String = ""
public init(data dictData: (dict: [String: [String]]?, path: String)) {
guard let theDict = dictData.dict else {
vCLog("↑ Exception happened when reading JSON file at: \(dictData.path).")
return
}
filePath = dictData.path
dataMap = theDict
}
public init(path: String) {
if path.isEmpty { return }
do {
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
if let rawJSON = try? JSONSerialization.jsonObject(with: rawData) as? [String: [String]] {
dataMap = rawJSON
} else {
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return
}
} catch {
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return
}
filePath = path
}
public func query(with kanji: String) -> [String]? {
guard let resultData = dataMap[kanji] else { return nil }
let resultArray = resultData.compactMap {
let result = restorePhonabetFromASCII($0)
return result.isEmpty ? nil : result
}
return resultArray.isEmpty ? nil : resultArray
}
///
///
/// ASCII
/// - parameters:
/// - incoming:
func restorePhonabetFromASCII(_ incoming: String) -> String {
var strOutput = incoming
if !strOutput.contains("_") {
for entry in Self.dicPhonabet4ASCII {
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
}
}
return strOutput
}
// MARK: - Constants
static let dicPhonabet4ASCII: [String: String] = [
"b": "", "p": "", "m": "", "f": "", "d": "", "t": "", "n": "", "l": "", "g": "", "k": "", "h": "",
"j": "", "q": "", "x": "", "Z": "", "C": "", "S": "", "r": "", "z": "", "c": "", "s": "", "i": "",
"u": "", "v": "", "a": "", "o": "", "e": "", "E": "", "B": "", "P": "", "M": "", "F": "", "D": "",
"T": "", "N": "", "L": "", "R": "", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "˙",
]
}
}

View File

@ -9,41 +9,74 @@
import Foundation
import Megrez
import Shared
// MARK: - Public Types.
public extension LMAssembly {
struct OverrideSuggestion {
public var candidates = [(String, Megrez.Unigram)]()
public var forceHighScoreOverride = false
public var isEmpty: Bool { candidates.isEmpty }
}
}
// MARK: - LMUserOverride Class Definition.
extension LMAssembly {
public extension vChewingLM {
class LMUserOverride {
// MARK: - Main
var mutCapacity: Int
var mutDecayExponent: Double
var mutLRUList: [KeyObservationPair] = []
var mutLRUMap: [String: KeyObservationPair] = [:]
let kDecayThreshold: Double = 1.0 / 1_048_576.0 //
var fileSaveLocationURL: URL?
var fileSaveLocationURL: URL
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL? = nil) {
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL) {
mutCapacity = max(capacity, 1) // Ensures that this integer value is always > 0.
mutDecayExponent = log(0.5) / decayConstant
fileSaveLocationURL = dataURL
}
public func performObservation(
walkedBefore: [Megrez.Node], walkedAfter: [Megrez.Node],
cursor: Int, timestamp: Double, saveCallback: @escaping () -> Void
) {
//
guard !walkedAfter.isEmpty, !walkedBefore.isEmpty else { return }
guard walkedBefore.totalKeyCount == walkedAfter.totalKeyCount else { return }
//
var actualCursor = 0
guard let currentNode = walkedAfter.findNode(at: cursor, target: &actualCursor) else { return }
// 使
guard currentNode.spanLength <= 3 else { return }
//
guard actualCursor > 0 else { return } //
let currentNodeIndex = actualCursor
actualCursor -= 1
var prevNodeIndex = 0
guard let prevNode = walkedBefore.findNode(at: actualCursor, target: &prevNodeIndex) else { return }
let forceHighScoreOverride: Bool = currentNode.spanLength > prevNode.spanLength
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
let key: String = vChewingLM.LMUserOverride.formObservationKey(
walkedNodes: walkedAfter, headIndex: targetNodeIndex
)
guard !key.isEmpty else { return }
doObservation(
key: key, candidate: currentNode.currentUnigram.value, timestamp: timestamp,
forceHighScoreOverride: forceHighScoreOverride, saveCallback: { saveCallback() }
)
}
public func fetchSuggestion(
currentWalk: [Megrez.Node], cursor: Int, timestamp: Double
) -> Suggestion {
var headIndex = 0
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
let key = vChewingLM.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.joinedKey())
}
}
}
// MARK: - Private Structures
extension LMAssembly.LMUserOverride {
extension vChewingLM.LMUserOverride {
enum OverrideUnit: CodingKey { case count, timestamp, forceHighScoreOverride }
enum ObservationUnit: CodingKey { case count, overrides }
enum KeyObservationPairUnit: CodingKey { case key, observation }
@ -120,52 +153,10 @@ extension LMAssembly.LMUserOverride {
}
}
// MARK: - Internal Methods in LMAssembly.
// MARK: - Hash and Dehash the entire UOM data, etc.
extension LMAssembly.LMUserOverride {
func performObservation(
walkedBefore: [Megrez.Node], walkedAfter: [Megrez.Node],
cursor: Int, timestamp: Double, saveCallback: (() -> Void)? = nil
) {
//
guard !walkedAfter.isEmpty, !walkedBefore.isEmpty else { return }
guard walkedBefore.totalKeyCount == walkedAfter.totalKeyCount else { return }
//
var actualCursor = 0
guard let currentNode = walkedAfter.findNode(at: cursor, target: &actualCursor) else { return }
// 使
guard currentNode.spanLength <= 3 else { return }
//
guard actualCursor > 0 else { return } //
let currentNodeIndex = actualCursor
actualCursor -= 1
var prevNodeIndex = 0
guard let prevNode = walkedBefore.findNode(at: actualCursor, target: &prevNodeIndex) else { return }
let forceHighScoreOverride: Bool = currentNode.spanLength > prevNode.spanLength
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
let key: String = LMAssembly.LMUserOverride.formObservationKey(
walkedNodes: walkedAfter, headIndex: targetNodeIndex
)
guard !key.isEmpty else { return }
doObservation(
key: key, candidate: currentNode.currentUnigram.value, timestamp: timestamp,
forceHighScoreOverride: forceHighScoreOverride, saveCallback: saveCallback
)
}
func fetchSuggestion(
currentWalk: [Megrez.Node], cursor: Int, timestamp: Double
) -> LMAssembly.OverrideSuggestion {
var headIndex = 0
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
let key = LMAssembly.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.joinedKey())
}
func bleachSpecifiedSuggestions(targets: [String], saveCallback: (() -> Void)? = nil) {
public extension vChewingLM.LMUserOverride {
func bleachSpecifiedSuggestions(targets: [String], saveCallback: @escaping () -> Void) {
if targets.isEmpty { return }
for neta in mutLRUMap {
for target in targets {
@ -175,86 +166,82 @@ extension LMAssembly.LMUserOverride {
}
}
resetMRUList()
saveCallback?() ?? saveData()
saveCallback()
}
/// LRU
func bleachUnigrams(saveCallback: (() -> Void)? = nil) {
func bleachUnigrams(saveCallback: @escaping () -> Void) {
for key in mutLRUMap.keys {
if !key.contains("(),()") { continue }
mutLRUMap.removeValue(forKey: key)
}
resetMRUList()
saveCallback?() ?? saveData()
saveCallback()
}
func resetMRUList() {
internal func resetMRUList() {
mutLRUList.removeAll()
for neta in mutLRUMap.reversed() {
mutLRUList.append(neta.value)
}
}
func clearData(withURL fileURL: URL? = nil) {
func clearData(withURL fileURL: URL) {
mutLRUMap = .init()
mutLRUList = .init()
do {
let nullData = "{}"
guard let fileURL = fileURL ?? fileSaveLocationURL else {
throw UOMError(rawValue: "given fileURL is invalid or nil.")
}
try nullData.write(to: fileURL, atomically: false, encoding: .utf8)
} catch {
vCLMLog("UOM Error: Unable to clear the data in the UOM file. Details: \(error)")
vCLog("UOM Error: Unable to clear data. Details: \(error)")
return
}
}
func saveData(toURL fileURL: URL? = nil) {
guard let fileURL: URL = fileURL ?? fileSaveLocationURL else {
vCLMLog("UOM saveData() failed. At least the file Save URL is not set for the current UOM.")
return
}
// 使 JSONSerialization
let encoder = JSONEncoder()
do {
guard let jsonData = try? encoder.encode(mutLRUMap) else { return }
let fileURL: URL = fileURL ?? fileSaveLocationURL
try jsonData.write(to: fileURL, options: .atomic)
} catch {
vCLMLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
vCLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
return
}
}
func loadData(fromURL fileURL: URL? = nil) {
guard let fileURL: URL = fileURL ?? fileSaveLocationURL else {
vCLMLog("UOM loadData() failed. At least the file Load URL is not set for the current UOM.")
return
}
func loadData(fromURL fileURL: URL) {
// 使 JSONSerialization
let decoder = JSONDecoder()
do {
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
if ["", "{}"].contains(String(data: data, encoding: .utf8)) { return }
guard let jsonResult = try? decoder.decode([String: KeyObservationPair].self, from: data) else {
vCLMLog("UOM Error: Read file content type invalid, abort loading.")
vCLog("UOM Error: Read file content type invalid, abort loading.")
return
}
mutLRUMap = jsonResult
resetMRUList()
} catch {
vCLMLog("UOM Error: Unable to read file or parse the data, abort loading. Details: \(error)")
vCLog("UOM Error: Unable to read file or parse the data, abort loading. Details: \(error)")
return
}
}
struct Suggestion {
public var candidates = [(String, Megrez.Unigram)]()
public var forceHighScoreOverride = false
public var isEmpty: Bool { candidates.isEmpty }
}
}
// MARK: - Other Non-Public Internal Methods
// MARK: - Private Methods
extension LMAssembly.LMUserOverride {
extension vChewingLM.LMUserOverride {
func doObservation(
key: String, candidate: String, timestamp: Double, forceHighScoreOverride: Bool,
saveCallback: (() -> Void)?
saveCallback: @escaping () -> Void
) {
guard mutLRUMap[key] != nil else {
var observation: Observation = .init()
@ -270,8 +257,8 @@ extension LMAssembly.LMUserOverride {
mutLRUMap.removeValue(forKey: mutLRUList[mutLRUList.endIndex - 1].key)
mutLRUList.removeLast()
}
vCLMLog("UOM: Observation finished with new observation: \(key)")
saveCallback?() ?? saveData()
vCLog("UOM: Observation finished with new observation: \(key)")
saveCallback()
return
}
// decayCallback
@ -281,12 +268,12 @@ extension LMAssembly.LMUserOverride {
)
mutLRUList.insert(theNeta, at: 0)
mutLRUMap[key] = theNeta
vCLMLog("UOM: Observation finished with existing observation: \(key)")
saveCallback?() ?? saveData()
vCLog("UOM: Observation finished with existing observation: \(key)")
saveCallback()
}
}
func getSuggestion(key: String, timestamp: Double, headReading: String) -> LMAssembly.OverrideSuggestion {
func getSuggestion(key: String, timestamp: Double, headReading: String) -> Suggestion {
guard !key.isEmpty, let kvPair = mutLRUMap[key] else { return .init() }
let observation: Observation = kvPair.observation
var candidates: [(String, Megrez.Unigram)] = .init()
@ -399,10 +386,3 @@ extension LMAssembly.LMUserOverride {
return result
}
}
struct UOMError: LocalizedError {
var rawValue: String
var errorDescription: String? {
NSLocalizedString("rawValue", comment: "")
}
}

View File

@ -1,5 +1,9 @@
// libTaBE (http://sourceforge.net/projects/libtabe/)
// (2002 ). 1999 Pai-Hsiang Hsiao BSD
//
// File.swift
//
//
// Created by ShikiSuen on 2023/11/26.
//
import Foundation
@ -25,8 +29,6 @@ INSERT INTO DATA_MAIN VALUES('de5','-3.516024 的\t-7.427179 得','-3.516024 的
INSERT INTO DATA_MAIN VALUES('di2','-3.516024 ','-3.516024 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('di4','-3.516024 ','-3.516024 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('duP3','-9.544 ','-9.544 ','\t䇏\t𦞙\t謉\t𠡒\t𡑈\t𥫉\t𦞱\t𧫏\t𩛔','','','');
INSERT INTO DATA_MAIN VALUES('uP','-6.0 ','-6.0 ',NULL,NULL,NULL,NULL); /* CNS */
INSERT INTO DATA_MAIN VALUES('uP2','-6.0 ','-6.0 ','-6.0 ',NULL,NULL,NULL); /* CNS */
INSERT INTO DATA_MAIN VALUES('fL','-11.0 🐝','-11.0 🐝',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('gM','-7.171551 \t-11.92872 \t-13.624335 \t-12.390804 ','-7.171551 \t-11.92872 \t-13.624335 \t-12.390804 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('gM-ke-ji4','-9.842421 ','-9.842421 ',NULL,NULL,NULL,NULL);

View File

@ -7,9 +7,10 @@
// requirements defined in MIT License.
import Foundation
import Shared
import SQLite3
public enum LMAssembly {
public enum vChewingLM {
enum FileErrors: Error {
case fileHandleError(String)
}
@ -50,12 +51,11 @@ extension Array where Element == String {
assert(looseEnds)
}
var ptrStmt: OpaquePointer?
defer { sqlite3_finalize(ptrStmt) }
for strStmt in self {
let thisResult = performStatement { ptrStmt in
sqlite3_prepare_v2(ptrDB, strStmt, -1, &ptrStmt, nil) == SQLITE_OK && sqlite3_step(ptrStmt) == SQLITE_DONE
}
guard thisResult else {
vCLMLog("SQL Query Error. Statement: \(strStmt)")
guard sqlite3_prepare_v2(ptrDB, strStmt, -1, &ptrStmt, nil) == SQLITE_OK, sqlite3_step(ptrStmt) == SQLITE_DONE else {
vCLog("SQL Query Error. Statement: \(strStmt)")
return false
}
}
@ -82,13 +82,3 @@ func performStatementSansResult(_ handler: (inout OpaquePointer?) -> Void) {
}
handler(&ptrStmt)
}
func vCLMLog(_ strPrint: StringLiteralType) {
guard let toLog = UserDefaults.standard.object(forKey: "_DebugMode") as? Bool else {
NSLog("vChewingDebug: %@", strPrint)
return
}
if toLog {
NSLog("vChewingDebug: %@", strPrint)
}
}

View File

@ -57,12 +57,10 @@ final class InputTokenTests: XCTestCase {
}
func testGeneratedResultsFromLMInstantiator() throws {
let instance = LMAssembly.LMInstantiator(isCHS: true)
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
instance.setOptions { config in
config.isCNSEnabled = false
config.isSymbolEnabled = false
}
let instance = vChewingLM.LMInstantiator(isCHS: true)
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
instance.isCNSEnabled = false
instance.isSymbolEnabled = false
instance.insertTemporaryData(
keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"],
unigram: .init(value: "MACRO@DATE_YEARDELTA:-1945", score: -97.5),
@ -70,6 +68,6 @@ final class InputTokenTests: XCTestCase {
)
let x = instance.unigramsFor(keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"]).description
print(x)
LMAssembly.LMInstantiator.disconnectSQLDB()
vChewingLM.LMInstantiator.disconnectSQLDB()
}
}

View File

@ -20,7 +20,7 @@ private let testDataPath: String = packageRootPath + "/Tests/TestCINData/"
final class LMCassetteTests: XCTestCase {
func testCassetteLoadWubi86() throws {
let pathCINFile = testDataPath + "wubi.cin"
var lmCassette = LMAssembly.LMCassette()
var lmCassette = vChewingLM.LMCassette()
NSLog("LMCassette: Start loading CIN.")
lmCassette.open(pathCINFile)
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")
@ -41,7 +41,7 @@ final class LMCassetteTests: XCTestCase {
func testCassetteLoadArray30() throws {
let pathCINFile = testDataPath + "array30.cin2"
var lmCassette = LMAssembly.LMCassette()
var lmCassette = vChewingLM.LMCassette()
NSLog("LMCassette: Start loading CIN.")
lmCassette.open(pathCINFile)
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")

View File

@ -38,7 +38,7 @@ private let sampleData: String = #"""
final class LMCoreEXTests: XCTestCase {
func testLMCoreEXAsFactoryCoreDict() throws {
var lmTest = LMAssembly.LMCoreEX(
var lmTest = vChewingLM.LMCoreEX(
reverse: false, consolidate: false, defaultScore: 0, forceDefaultScore: false
)
lmTest.replaceData(textData: sampleData)

View File

@ -22,43 +22,22 @@ private let expectedReverseLookupResults: [String] = [
final class LMInstantiatorSQLTests: XCTestCase {
func testSQL() throws {
let instance = LMAssembly.LMInstantiator(isCHS: true)
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
instance.setOptions { config in
config.isCNSEnabled = false
config.isSymbolEnabled = false
}
let instance = vChewingLM.LMInstantiator(isCHS: true)
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
instance.isCNSEnabled = false
instance.isSymbolEnabled = false
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).description, "[(吹牛逼,-7.375), (吹牛屄,-7.399)]")
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)")
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).description, "[(㨃,-9.544)]")
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).description, "[(ㄋㄟㄋㄟ,-1.0)]")
instance.setOptions { config in
config.isCNSEnabled = true
config.isSymbolEnabled = true
}
instance.isCNSEnabled = true
instance.isSymbolEnabled = true
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).last?.description, "(🌳🆕🐝,-13.0)")
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)")
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10)
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).last?.description, "(☉☉,-13.0)")
//
XCTAssertEqual(LMAssembly.LMInstantiator.getFactoryReverseLookupData(with: ""), expectedReverseLookupResults)
LMAssembly.LMInstantiator.disconnectSQLDB()
}
func testCNSMask() throws {
let instance = LMAssembly.LMInstantiator(isCHS: false)
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
instance.setOptions { config in
config.isCNSEnabled = false
config.isSymbolEnabled = false
config.filterNonCNSReadings = false
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟ"]).description, "[(危,-6.0)]")
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟˊ"]).description, "[(危,-6.0)]")
instance.setOptions { config in
config.filterNonCNSReadings = true
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟ"]).description, "[]")
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟˊ"]).description, "[(危,-6.0)]")
XCTAssertEqual(vChewingLM.LMInstantiator.getFactoryReverseLookupData(with: ""), expectedReverseLookupResults)
vChewingLM.LMInstantiator.disconnectSQLDB()
}
}

View File

@ -1,36 +0,0 @@
//// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// StringView Ranges extension by (c) 2022 and onwards Isaac Xen (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import XCTest
@testable import LangModelAssembly
final class LMPlainBPMFTests: XCTestCase {
func testLMPlainBPMFDataQuery() throws {
let instance1 = LMAssembly.LMInstantiator(isCHS: false).setOptions { config in
config.isSCPCEnabled = true
}
var liu2 = instance1.unigramsFor(keyArray: ["ㄌㄧㄡˊ"]).map(\.value).prefix(3)
var bao3 = instance1.unigramsFor(keyArray: ["ㄅㄠˇ"]).map(\.value).prefix(3)
var jie2 = instance1.unigramsFor(keyArray: ["ㄐㄧㄝˊ"]).map(\.value).prefix(3)
XCTAssertEqual(liu2, ["", "", ""])
XCTAssertEqual(bao3, ["", "", ""])
XCTAssertEqual(jie2, ["", "", ""])
let instance2 = LMAssembly.LMInstantiator(isCHS: true).setOptions { config in
config.isSCPCEnabled = true
}
liu2 = instance2.unigramsFor(keyArray: ["ㄌㄧㄡˊ"]).map(\.value).prefix(3)
bao3 = instance2.unigramsFor(keyArray: ["ㄅㄠˇ"]).map(\.value).prefix(3)
jie2 = instance2.unigramsFor(keyArray: ["ㄐㄧㄝˊ"]).map(\.value).prefix(3)
XCTAssertEqual(liu2, ["", "", ""])
XCTAssertEqual(bao3, ["", "", ""])
XCTAssertEqual(jie2, ["", "", ""])
}
}

View File

@ -17,12 +17,12 @@ private let halfLife: Double = 5400
private let nullURL = URL(fileURLWithPath: "/dev/null")
final class LMUserOverrideTests: XCTestCase {
private func observe(who uom: LMAssembly.LMUserOverride, key: String, candidate: String, timestamp stamp: Double) {
private func observe(who uom: vChewingLM.LMUserOverride, key: String, candidate: String, timestamp stamp: Double) {
uom.doObservation(key: key, candidate: candidate, timestamp: stamp, forceHighScoreOverride: false, saveCallback: {})
}
func testUOM_1_BasicOps() throws {
let uom = LMAssembly.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
let headReading = "ㄍㄡˇ"
let expectedSuggestion = ""
@ -45,7 +45,7 @@ final class LMUserOverrideTests: XCTestCase {
}
func testUOM_2_NewestAgainstRepeatedlyUsed() throws {
let uom = LMAssembly.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
let headReading = "ㄍㄡˇ"
let valRepeatedlyUsed = "" //
@ -74,7 +74,7 @@ final class LMUserOverrideTests: XCTestCase {
let b = (key: "((ㄆㄞˋ-ㄇㄥˊ,派蒙),(ㄉㄜ˙,的),ㄐㄧㄤˇ-ㄐㄧㄣ)", value: "伙食費", head: "ㄏㄨㄛˇ-ㄕˊ-ㄈㄟˋ")
let c = (key: "((ㄍㄨㄛˊ-ㄅㄥ,國崩),(ㄉㄜ˙,的),ㄇㄠˋ-ㄗ˙)", value: "帽子", head: "ㄇㄠˋ-ㄗ˙")
let d = (key: "((ㄌㄟˊ-ㄉㄧㄢˋ-ㄐㄧㄤ-ㄐㄩㄣ,雷電將軍),(ㄉㄜ˙,的),ㄐㄧㄠˇ-ㄔㄡˋ)", value: "腳臭", head: "ㄐㄧㄠˇ-ㄔㄡˋ")
let uom = LMAssembly.LMUserOverride(capacity: 2, decayConstant: Double(halfLife), dataURL: nullURL)
let uom = vChewingLM.LMUserOverride(capacity: 2, decayConstant: Double(halfLife), dataURL: nullURL)
observe(who: uom, key: a.key, candidate: a.value, timestamp: nowTimeStamp)
observe(who: uom, key: b.key, candidate: b.value, timestamp: nowTimeStamp + halfLife * 1)
observe(who: uom, key: c.key, candidate: c.value, timestamp: nowTimeStamp + halfLife * 2)

View File

@ -1,30 +0,0 @@
//// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import XCTest
@testable import LangModelAssembly
final class LMInstantiatorNumericPadTests: XCTestCase {
func testSQL() throws {
let instance = LMAssembly.LMInstantiator(isCHS: true)
instance.setOptions { config in
config.numPadFWHWStatus = nil
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["_NumPad_0"]).description, "[]")
instance.setOptions { config in
config.numPadFWHWStatus = true
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["_NumPad_0"]).description, "[(,0.0), (0,-0.1)]")
instance.setOptions { config in
config.numPadFWHWStatus = false
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["_NumPad_0"]).description, "[(0,0.0), (,-0.1)]")
}
}

View File

@ -17,12 +17,10 @@ let package = Package(
.package(path: "../HangarRash_SwiftyCapsLockToggler"),
.package(path: "../Jad_BookmarkManager"),
.package(path: "../Qwertyyb_ShiftKeyUpChecker"),
.package(path: "../vChewing_BrailleSputnik"),
.package(path: "../vChewing_CandidateWindow"),
.package(path: "../vChewing_OSFrameworkImpl"),
.package(path: "../vChewing_CocoaExtension"),
.package(path: "../vChewing_Hotenka"),
.package(path: "../vChewing_IMKUtils"),
.package(path: "../vChewing_KimoDataReader"),
.package(path: "../vChewing_LangModelAssembly"),
.package(path: "../vChewing_Megrez"),
.package(path: "../vChewing_NotifierUI"),
@ -39,14 +37,12 @@ let package = Package(
.target(
name: "MainAssembly",
dependencies: [
.product(name: "BrailleSputnik", package: "vChewing_BrailleSputnik"),
.product(name: "BookmarkManager", package: "Jad_BookmarkManager"),
.product(name: "CandidateWindow", package: "vChewing_CandidateWindow"),
.product(name: "OSFrameworkImpl", package: "vChewing_OSFrameworkImpl"),
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
.product(name: "FolderMonitor", package: "DanielGalasko_FolderMonitor"),
.product(name: "Hotenka", package: "vChewing_Hotenka"),
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
.product(name: "KimoDataReader", package: "vChewing_KimoDataReader"),
.product(name: "LangModelAssembly", package: "vChewing_LangModelAssembly"),
.product(name: "Megrez", package: "vChewing_Megrez"),
.product(name: "NotifierUI", package: "vChewing_NotifierUI"),

View File

@ -7,41 +7,23 @@
// requirements defined in MIT License.
import AppKit
import Shared
import SwiftUI
@available(macOS 12, *)
public class CtlAboutUI: NSWindowController, NSWindowDelegate {
public static var shared: CtlAboutUI?
private var viewController: NSViewController?
var useLegacyView: Bool = false
public init(forceLegacy: Bool = false) {
useLegacyView = forceLegacy
let newWindow = NSWindow(
contentRect: CGRect(x: 401, y: 295, width: 577, height: 568),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered, defer: true
)
super.init(window: newWindow)
guard #available(macOS 12, *), !useLegacyView else {
viewController = VwrAboutCocoa()
viewController?.loadView()
return
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
public static func show() {
let forceLegacy = NSEvent.modifierFlags == .option
if shared == nil {
let newInstance = CtlAboutUI(forceLegacy: forceLegacy)
let newWindow = NSWindow(
contentRect: CGRect(x: 401, y: 295, width: 577, height: 568),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered, defer: true
)
let newInstance = CtlAboutUI(window: newWindow)
shared = newInstance
}
guard let shared = shared, let sharedWindow = shared.window else { return }
shared.useLegacyView = forceLegacy
sharedWindow.delegate = shared
if !sharedWindow.isVisible {
shared.windowDidLoad()
@ -55,28 +37,6 @@ public class CtlAboutUI: NSWindowController, NSWindowDelegate {
override public func windowDidLoad() {
super.windowDidLoad()
guard let window = window else { return }
if #available(macOS 12, *), !useLegacyView {
windowDidLoadSwiftUI()
return
}
let theViewController = viewController ?? VwrAboutCocoa()
viewController = theViewController
window.contentViewController = viewController
let size = theViewController.view.fittingSize
window.setPosition(vertical: .top, horizontal: .left, padding: 20)
window.setFrame(.init(origin: window.frame.origin, size: size), display: true)
window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
if #available(macOS 10.10, *) {
window.titlebarAppearsTransparent = true
}
window.title = "i18n:aboutWindow.ABOUT_APP_TITLE_FULL".localized + " (v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")))"
}
@available(macOS 12, *)
private func windowDidLoadSwiftUI() {
window?.setPosition(vertical: .top, horizontal: .left, padding: 20)
window?.standardWindowButton(.closeButton)?.isHidden = true
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true

View File

@ -1,215 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import Foundation
import Shared
public extension VwrAboutCocoa {
static let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL"
static let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String ?? "BAD_EULA_CONTENT"
static let eulaContentUpstream = Bundle.main.infoDictionary?["CFUpstreamEULAContent"] as? String ?? "BAD_EULA_UPSTREAM"
}
public class VwrAboutCocoa: NSViewController {
let windowWidth: CGFloat = 533
let contentWidth: CGFloat = 510
let imgWidth: CGFloat = 63
override public func loadView() {
view = body ?? .init()
(view as? NSStackView)?.alignment = .centerX
view.makeSimpleConstraint(.width, relation: .equal, value: windowWidth)
}
var appNameAndVersionString: NSAttributedString {
let strResult = NSMutableAttributedString(string: "i18n:aboutWindow.APP_NAME".localized)
strResult.addAttribute(
.font,
value: {
if #available(macOS 10.11, *) {
return NSFont.systemFont(ofSize: 12, weight: .bold)
}
return NSFont.boldSystemFont(ofSize: 12)
}(),
range: .init(location: 0, length: strResult.length)
)
let strVersion = NSMutableAttributedString(string: " \(versionString)")
strVersion.addAttribute(
.font,
value: NSFont.systemFont(ofSize: 11),
range: .init(location: 0, length: strVersion.length)
)
strResult.append(strVersion)
strResult.addAttribute(
.kern,
value: 0,
range: .init(location: 0, length: strResult.length)
)
return strResult
}
var body: NSView? {
NSStackView.buildSection(width: contentWidth - 18) {
NSStackView.build(.horizontal) {
bannerImage
NSStackView.build(.vertical) {
appNameAndVersionString.makeNSLabel(fixWidth: contentWidth - imgWidth - 10)
makeFormattedLabel(
verbatim: "i18n:aboutWindow.APP_DERIVED_FROM".localized
+ "\n"
+ Self.copyrightLabel,
size: 11,
isBold: false, fixWidth: contentWidth - imgWidth - 10
)
makeFormattedLabel(
verbatim: "i18n:aboutWindow.DEV_CREW".localized,
size: 11,
isBold: false, fixWidth: contentWidth - imgWidth - 10
)
makeFormattedLabel(
verbatim: "i18n:aboutWindow.LICENSE_TITLE".localized,
size: 11,
isBold: false, fixWidth: contentWidth - imgWidth - 10
)
eulaBox
}
}
NSStackView.build(.horizontal) {
NSStackView.build(.vertical) {
"i18n:aboutWindow.DISCLAIMER_TEXT".makeNSLabel(
descriptive: true, fixWidth: contentWidth - 120
)
NSView()
}
var verticalButtonStackSpacing: CGFloat? = 4
if #unavailable(macOS 10.10) {
verticalButtonStackSpacing = nil
}
NSStackView.build(.vertical, spacing: verticalButtonStackSpacing, width: 114) {
addKeyEquivalent(
NSButton(
"i18n:aboutWindow.OK_BUTTON",
target: self, action: #selector(btnOKAction(_:))
)
)
NSButton(
"i18n:aboutWindow.WEBSITE_BUTTON",
target: self, action: #selector(btnWebSiteAction(_:))
)
NSButton(
"i18n:aboutWindow.BUGREPORT_BUTTON",
target: self, action: #selector(btnBugReportAction(_:))
)
}
}
}?.withInsets(
{
if #available(macOS 10.10, *) {
return .new(all: 20, top: 0, bottom: 24)
} else {
return .new(all: 20, top: 10, bottom: 24)
}
}()
)
}
var versionString: String {
"v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")) - \(IMEApp.appSignedDateLabel)"
}
var bannerImage: NSImageView {
let maybeImg = NSImage(named: "AboutBanner")
let imgIsNull = maybeImg == nil
let img = maybeImg ?? .init(size: .init(width: 63, height: 310))
let result = NSImageView()
result.image = img
result.makeSimpleConstraint(.width, relation: .equal, value: 63)
result.makeSimpleConstraint(.height, relation: .equal, value: 310)
if imgIsNull {
result.wantsLayer = true
result.layer?.backgroundColor = NSColor.black.cgColor
}
return result
}
func makeFormattedLabel(
verbatim: String,
size: CGFloat = 12,
isBold: Bool = false,
fixWidth: CGFloat? = nil
) -> NSTextField {
let attrStr = NSMutableAttributedString(string: verbatim)
attrStr.addAttribute(
.kern,
value: 0,
range: .init(location: 0, length: attrStr.length)
)
attrStr.addAttribute(
.font,
value: {
guard isBold else { return NSFont.systemFont(ofSize: size) }
if #available(macOS 10.11, *) {
return NSFont.systemFont(ofSize: size, weight: .bold)
}
return NSFont.boldSystemFont(ofSize: size)
}(),
range: .init(location: 0, length: attrStr.length)
)
return attrStr.makeNSLabel(fixWidth: fixWidth)
}
var eulaBox: NSScrollView {
let textView = NSTextView()
let clipView = NSClipView()
let scrollView = NSScrollView()
textView.autoresizingMask = [.width, .height]
textView.isEditable = false
textView.isRichText = false
textView.isSelectable = true
textView.isVerticallyResizable = true
textView.smartInsertDeleteEnabled = true
textView.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
textView.string = Self.eulaContent + "\n" + Self.eulaContentUpstream
clipView.documentView = textView
clipView.autoresizingMask = [.width, .height]
clipView.drawsBackground = false
scrollView.contentView = clipView
scrollView.makeSimpleConstraint(.width, relation: .equal, value: 430)
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = false
scrollView.scrollerStyle = .legacy
return scrollView
}
@discardableResult func addKeyEquivalent(_ button: NSButton) -> NSButton {
button.keyEquivalent = String(NSEvent.SpecialKey.carriageReturn.unicodeScalar)
return button
}
@objc func btnOKAction(_: NSControl) {
CtlAboutUI.shared?.window?.close()
}
@objc func btnWebSiteAction(_: NSControl) {
if let url = URL(string: "https://vchewing.github.io/") {
NSWorkspace.shared.open(url)
}
}
@objc func btnBugReportAction(_: NSControl) {
if let url = URL(string: "https://vchewing.github.io/BUGREPORT.html") {
NSWorkspace.shared.open(url)
}
}
}
@available(macOS 14.0, *)
#Preview(traits: .fixedLayout(width: 533, height: 550)) {
VwrAboutCocoa()
}

View File

@ -7,18 +7,14 @@
// requirements defined in MIT License.
import AppKit
import Shared
import SwiftUI
public struct VwrAboutUI {
public static var copyrightLabel: String { VwrAboutCocoa.copyrightLabel }
public static var eulaContent: String { VwrAboutCocoa.eulaContent }
public static var eulaContentUpstream: String { VwrAboutCocoa.eulaContentUpstream }
let foobar = "FOO_BAR"
}
@available(macOS 12, *)
extension VwrAboutUI: View {
public struct VwrAboutUI: View {
public static let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL"
public static let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String ?? "BAD_EULA_CONTENT"
public static let eulaContentUpstream = Bundle.main.infoDictionary?["CFUpstreamEULAContent"] as? String ?? "BAD_EULA_UPSTREAM"
public var body: some View {
GroupBox {
VStack(alignment: .leading, spacing: 6) {
@ -33,7 +29,6 @@ extension VwrAboutUI: View {
Text("v\(IMEApp.appMainVersionLabel.joined(separator: " Build ")) - \(IMEApp.appSignedDateLabel)").lineLimit(1)
}.fixedSize()
Text("i18n:aboutWindow.APP_DERIVED_FROM").font(.custom("Tahoma", size: 11))
Text(Self.copyrightLabel).font(.custom("Tahoma", size: 11))
Text("i18n:aboutWindow.DEV_CREW").font(.custom("Tahoma", size: 11)).padding([.vertical], 2)
}
}

View File

@ -47,6 +47,7 @@ extension AppDelegate {
// initUserLangModels()
if PrefMgr.shared.shouldAutoReloadUserDataFiles || forced { LMMgr.initUserLangModels() }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
if #available(macOS 10.15, *) { FileObserveProject.shared.touch() }
if PrefMgr.shared.phraseEditorAutoReloadExternalModifications {
Broadcaster.shared.eventForReloadingPhraseEditor = .init()
}
@ -67,10 +68,6 @@ public extension AppDelegate {
SecurityAgentHelper.shared.timer?.fire()
SpeechSputnik.shared.refreshStatus() //
CandidateTextService.enableFinalSanityCheck()
// 使
// Debug
// Debug
@ -135,9 +132,12 @@ public extension AppDelegate {
NSApp.popup()
guard result == NSApplication.ModalResponse.alertFirstButtonReturn else { return }
let url = URL(fileURLWithPath: LMMgr.dataFolderPath(isDefaultFolder: true))
FileOpenMethod.finder.open(url: url)
guard let finderURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.finder") else { return }
let configuration = NSWorkspace.OpenConfiguration()
configuration.promptsUserIfNeeded = true
NSWorkspace.shared.open([url], withApplicationAt: finderURL, configuration: configuration)
Uninstaller.uninstall(
selfKill: true, defaultDataFolderPath: LMMgr.dataFolderPath(isDefaultFolder: true)
isSudo: false, selfKill: true, defaultDataFolderPath: LMMgr.dataFolderPath(isDefaultFolder: true)
)
}
@ -147,7 +147,7 @@ public extension AppDelegate {
guard let currentMemorySizeInBytes = NSApplication.memoryFootprint else { return 0 }
let currentMemorySize: Double = (Double(currentMemorySizeInBytes) / 1024 / 1024).rounded(toPlaces: 1)
switch currentMemorySize {
case 1024...:
case 384...:
vCLog("WARNING: EXCESSIVE MEMORY FOOTPRINT (\(currentMemorySize)MB).")
let msgPackage = UNMutableNotificationContent()
msgPackage.title = NSLocalizedString("vChewing", comment: "")
@ -169,10 +169,4 @@ public extension AppDelegate {
}
return currentMemorySize
}
// New About Window
@IBAction func about(_: Any) {
CtlAboutUI.show()
NSApp.popup()
}
}

View File

@ -1,170 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import BrailleSputnik
import Foundation
import Shared
import Tekkon
public extension CandidateTextService {
// MARK: - Final Sanity Check Implementation.
static func enableFinalSanityCheck() {
finalSanityCheck = finalSanityCheckImplemented
}
private static func finalSanityCheckImplemented(_ target: CandidateTextService) -> Bool {
switch target.value {
case .url: return true
case let .selector(strSelector):
guard target.candidateText != "%s" else { return true } //
switch strSelector {
case "copyUnicodeMetadata:": return true
case _ where strSelector.hasPrefix("copyRuby"),
_ where strSelector.hasPrefix("copyBraille"),
_ where strSelector.hasPrefix("copyInline"):
return !target.reading.joined().isEmpty // 便 [""]
default: return true
}
}
}
// MARK: - Selector Methods, CandidatePairServicable, and the Coordinator.
var responseFromSelector: String? {
switch value {
case .url: return nil
case let .selector(string):
let passable = CandidatePairServicable(value: candidateText, reading: reading)
return Coordinator().runTask(selectorName: string, candidate: passable)
}
}
@objcMembers class CandidatePairServicable: NSObject {
public var value: String
public var reading: [String]
public init(value: String, reading: [String] = []) {
self.value = value
self.reading = reading
}
public typealias SubPair = (key: String, value: String)
@nonobjc var smashed: [SubPair] {
var pairs = [SubPair]()
if value.count != reading.count {
pairs.append((reading.joined(separator: " "), value))
} else {
value.enumerated().forEach { i, valChar in
pairs.append((reading[i], valChar.description))
}
}
return pairs
}
}
@objc class Coordinator: NSObject {
private var result: String?
public func runTask(selectorName: String, candidate param: CandidatePairServicable) -> String? {
guard !selectorName.isEmpty, !param.value.isEmpty else { return nil }
guard responds(to: Selector(selectorName)) else { return nil }
performSelector(onMainThread: Selector(selectorName), with: param, waitUntilDone: true)
defer { result = nil }
return result
}
/// Unicode
/// - Parameter param:
@objc func copyUnicodeMetadata(_ param: CandidatePairServicable) {
var resultArray = [String]()
param.value.forEach { char in
resultArray.append("\(char) \(char.description.charDescriptions.first ?? "NULL")")
}
result = resultArray.joined(separator: "\n")
}
/// HTML Ruby ()
/// - Parameter param:
@objc func copyRubyHTMLZhuyinTextbookStyle(_ param: CandidatePairServicable) {
prepareTextBookZhuyinReadings(param)
copyRubyHTMLCommon(param)
}
/// HTML Ruby ()
/// - Parameter param:
@objc func copyRubyHTMLHanyuPinyinTextbookStyle(_ param: CandidatePairServicable) {
prepareTextBookPinyinReadings(param)
copyRubyHTMLCommon(param)
}
/// ()
/// - Parameter param:
@objc func copyInlineZhuyinAnnotationTextbookStyle(_ param: CandidatePairServicable) {
prepareTextBookZhuyinReadings(param)
copyInlineAnnotationCommon(param)
}
/// ()
/// - Parameter param:
@objc func copyInlineHanyuPinyinAnnotationTextbookStyle(_ param: CandidatePairServicable) {
prepareTextBookPinyinReadings(param)
copyInlineAnnotationCommon(param)
}
@objc func copyBraille1947(_ param: CandidatePairServicable) {
result = BrailleSputnik(standard: .of1947).convertToBraille(smashedPairs: param.smashed)
}
@objc func copyBraille2018(_ param: CandidatePairServicable) {
result = BrailleSputnik(standard: .of2018).convertToBraille(smashedPairs: param.smashed)
}
// MARK: Privates
}
}
private extension CandidateTextService.Coordinator {
func copyInlineAnnotationCommon(_ param: CandidateTextService.CandidatePairServicable) {
var composed = ""
param.smashed.forEach { subPair in
let subKey = subPair.key
let subValue = subPair.value
composed += subKey.contains("_") ? subValue : "\(subValue)(\(subKey))"
}
result = composed
}
func copyRubyHTMLCommon(_ param: CandidateTextService.CandidatePairServicable) {
var composed = ""
param.smashed.forEach { subPair in
let subKey = subPair.key
let subValue = subPair.value
composed += subKey.contains("_") ? subValue : "<ruby>\(subValue)<rp>(</rp><rt>\(subKey)</rt><rp>)</rp></ruby>"
}
result = composed
}
func prepareTextBookZhuyinReadings(_ param: CandidateTextService.CandidatePairServicable) {
let newReadings = param.reading.map { currentReading in
if currentReading.contains("_") { return "_??" }
return Tekkon.cnvPhonaToTextbookStyle(target: currentReading)
}
param.reading = newReadings
}
func prepareTextBookPinyinReadings(_ param: CandidateTextService.CandidatePairServicable) {
let newReadings = param.reading.map { currentReading in
if currentReading.contains("_") { return "_??" }
return Tekkon.cnvHanyuPinyinToTextbookStyle(
targetJoined: Tekkon.cnvPhonaToHanyuPinyin(targetJoined: currentReading)
)
}
param.reading = newReadings
}
}

View File

@ -7,7 +7,6 @@
// requirements defined in MIT License.
import Hotenka
import Shared
public enum ChineseConverter {
public static let shared = HotenkaChineseConverter(

View File

@ -1,62 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import Foundation
public class CtlClientListMgr: NSWindowController {
let viewController = VwrClientListMgr()
public static var shared: CtlClientListMgr?
public init() {
super.init(
window: .init(
contentRect: CGRect(x: 401, y: 295, width: 770, height: 335),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered,
defer: true
)
)
viewController.loadView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
public static func show() {
if shared == nil {
shared = CtlClientListMgr()
}
guard let shared = shared, let sharedWindow = shared.window else { return }
if !sharedWindow.isVisible {
shared.windowDidLoad()
}
sharedWindow.setPosition(vertical: .center, horizontal: .right, padding: 20)
sharedWindow.orderFrontRegardless() //
sharedWindow.title = "Client Manager".localized
sharedWindow.level = .statusBar
if #available(macOS 10.10, *) {
sharedWindow.titlebarAppearsTransparent = true
}
shared.showWindow(shared)
NSApp.popup()
}
override public func windowDidLoad() {
super.windowDidLoad()
let view = viewController.view
window?.contentView = view
if let window = window {
var frame = window.frame
frame.size = view.fittingSize
window.setFrame(frame, display: true)
}
window?.setPosition(vertical: .center, horizontal: .right, padding: 20)
}
}

View File

@ -8,6 +8,7 @@
import AppKit
import Carbon
import Shared
// MARK: - Top-level Enums relating to Input Mode and Language Supports.
@ -51,7 +52,7 @@ public enum IMEApp {
// MARK: -
public static var currentInputMode: Shared.InputMode {
.init(rawValue: PrefMgr().mostRecentInputMode) ?? .imeModeNULL
.init(rawValue: PrefMgr.shared.mostRecentInputMode) ?? .imeModeNULL
}
/// JIS
@ -61,10 +62,9 @@ public enum IMEApp {
/// Fart or Beep?
public static func buzz() {
let prefs = PrefMgr()
if prefs.isDebugModeEnabled {
NSSound.buzz(fart: !prefs.shouldNotFartInLieuOfBeep)
} else if !prefs.shouldNotFartInLieuOfBeep {
if PrefMgr.shared.isDebugModeEnabled {
NSSound.buzz(fart: !PrefMgr.shared.shouldNotFartInLieuOfBeep)
} else if !PrefMgr.shared.shouldNotFartInLieuOfBeep {
NSSound.buzz(fart: true)
} else {
NSSound.beep()

View File

@ -33,7 +33,7 @@ import Shared
/// /
/// 使
///
/// - ** .ofAssociates**:
/// - ** .ofAssociates**:
/// - ** .ofAbortion**: .ofEmpty()
/// .ofEmpty()
/// - ** .ofCommitting**:
@ -200,8 +200,7 @@ public extension IMEState {
case .ofCandidates where cursor != marker: return data.attributedStringMarking(for: session)
case .ofCandidates where cursor == marker: break
case .ofAssociates: return data.attributedStringPlaceholder(for: session)
case .ofSymbolTable where displayedText.isEmpty || node.containsCandidateServices:
return data.attributedStringPlaceholder(for: session)
case .ofSymbolTable where displayedText.isEmpty: return data.attributedStringPlaceholder(for: session)
case .ofSymbolTable where !displayedText.isEmpty: break
default: break
}

View File

@ -212,7 +212,7 @@ public extension IMEStateData {
subNeta = Tekkon.cnvPhonaToHanyuPinyin(targetJoined: subNeta)
subNeta = Tekkon.cnvHanyuPinyinToTextbookStyle(targetJoined: subNeta)
} else {
subNeta = Tekkon.cnvPhonaToTextbookStyle(target: subNeta)
subNeta = Tekkon.cnvPhonaToTextbookReading(target: subNeta)
}
}
arrOutput.append(subNeta)

View File

@ -1,125 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import BrailleSputnik
import Shared
import Tekkon
///
extension InputHandler {
// MARK: - (Shift+)Ctrl+Command+Enter
/// Command+Enter
/// - Parameter isShiftPressed: Shift
/// - Returns: SessionCtl IMK
func commissionByCtrlCommandEnter(isShiftPressed: Bool = false) -> String {
var displayedText = compositor.keys.joined(separator: "\t")
if compositor.isEmpty {
displayedText = readingForDisplay
}
if !prefs.cassetteEnabled {
if prefs.inlineDumpPinyinInLieuOfZhuyin {
if !compositor.isEmpty {
var arrDisplayedTextElements = [String]()
compositor.keys.forEach { key in
arrDisplayedTextElements.append(Tekkon.restoreToneOneInPhona(target: key)) //
}
displayedText = arrDisplayedTextElements.joined(separator: "\t")
}
displayedText = Tekkon.cnvPhonaToHanyuPinyin(targetJoined: displayedText) //
}
if prefs.showHanyuPinyinInCompositionBuffer {
if compositor.isEmpty {
displayedText = displayedText.replacingOccurrences(of: "1", with: "")
}
}
}
displayedText = displayedText.replacingOccurrences(of: "\t", with: isShiftPressed ? "-" : " ")
return displayedText
}
// MARK: - (Shift+)Ctrl+Command+Option+Enter Ruby
private enum CommitableMarkupType: Int {
case bareKeys = -1
case textWithBracketedAnnotations = 0
case textWithHTMLRubyAnnotations = 1
case braille1947 = 2
case braille2018 = 3
static func match(rawValue: Int) -> Self {
CommitableMarkupType(rawValue: rawValue) ?? .textWithBracketedAnnotations
}
var brailleStandard: BrailleSputnik.BrailleStandard? {
switch self {
case .braille1947: return .of1947
case .braille2018: return .of2018
default: return nil
}
}
}
/// Command+Option+Enter Ruby
///
/// prefs.specifyCmdOptCtrlEnterBehavior
/// 1.
/// 2. HTML Ruby
/// 3. (1947)
/// 4. (GF0019-2018)
/// - Parameter isShiftPressed: Shift
/// - Returns: SessionCtl IMK
func commissionByCtrlOptionCommandEnter(isShiftPressed: Bool = false) -> String {
var behavior = CommitableMarkupType.match(rawValue: prefs.specifyCmdOptCtrlEnterBehavior)
if prefs.cassetteEnabled, behavior.brailleStandard != nil {
behavior = .textWithBracketedAnnotations
}
if isShiftPressed { behavior = .bareKeys }
guard let brailleStandard = behavior.brailleStandard else {
return specifyTextMarkupToCommit(behavior: behavior)
}
let brailleProcessor = BrailleSputnik(standard: brailleStandard)
return brailleProcessor.convertToBraille(
smashedPairs: compositor.walkedNodes.smashedPairs,
extraInsertion: (reading: composer.value, cursor: compositor.cursor)
)
}
private func specifyTextMarkupToCommit(behavior: CommitableMarkupType) -> String {
var composed = ""
compositor.walkedNodes.smashedPairs.forEach { key, value in
var key = key
if !prefs.cassetteEnabled {
key =
prefs.inlineDumpPinyinInLieuOfZhuyin
? Tekkon.restoreToneOneInPhona(target: key) //
: Tekkon.cnvPhonaToTextbookStyle(target: key) //
if prefs.inlineDumpPinyinInLieuOfZhuyin {
key = Tekkon.cnvPhonaToHanyuPinyin(targetJoined: key) //
key = Tekkon.cnvHanyuPinyinToTextbookStyle(targetJoined: key) // 調
}
}
key = key.replacingOccurrences(of: "\t", with: " ")
switch behavior {
case .bareKeys:
if !composed.isEmpty { composed += " " }
composed += key.contains("_") ? "??" : key
case .textWithBracketedAnnotations:
composed += key.contains("_") ? value : "\(value)(\(key))"
case .textWithHTMLRubyAnnotations:
composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>"
case .braille1947: break //
case .braille2018: break //
}
}
return composed
}
}

View File

@ -1,77 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Shared
import SwiftExtension
// MARK: - Typing Method
public extension InputHandler {
enum TypingMethod: Int, CaseIterable {
case vChewingFactory // : 0
case codePoint // : 1
case haninKeyboardSymbol // : 2
mutating func revolveNext() {
var theInt = rawValue
theInt.revolveAsIndex(with: Self.allCases)
guard let nextMethod = TypingMethod(rawValue: theInt) else { return }
self = nextMethod
}
func getTooltip(vertical: Bool = false) -> String {
switch self {
case .vChewingFactory: return ""
case .codePoint:
let commonTerm = NSMutableString()
commonTerm.insert("Code Point Input.".localized, at: 0)
if !vertical, let initials = IMEApp.currentInputMode.nonUTFEncodingInitials {
commonTerm.insert("[\(initials)] ", at: 0)
}
return commonTerm.description
case .haninKeyboardSymbol:
return "\("Hanin Keyboard Symbol Input.".localized)"
}
}
}
}
// MARK: - Handle Rotation Toggles
public extension InputHandler {
@discardableResult func revolveTypingMethod(to specifiedMethod: TypingMethod? = nil) -> Bool {
guard let delegate = delegate else { return false }
var newMethod = currentTypingMethod
if let specified = specifiedMethod {
newMethod = specified
} else {
newMethod.revolveNext()
}
/// defer
/// switch newMethod currentTypingMethod
defer {
currentTypingMethod = newMethod
}
switch newMethod {
case .vChewingFactory:
delegate.switchState(IMEState.ofAbortion())
return true
case .codePoint:
strCodePointBuffer.removeAll()
case .haninKeyboardSymbol: break
}
var updatedState = generateStateOfInputting(sansReading: true)
delegate.switchState(IMEState.ofCommitting(textToCommit: updatedState.displayedText))
updatedState = generateStateOfInputting(guarded: true)
updatedState.tooltipDuration = 0
updatedState.tooltip = newMethod.getTooltip(vertical: delegate.isVerticalTyping)
delegate.switchState(updatedState)
return true
}
}

View File

@ -19,8 +19,10 @@ import Tekkon
// MARK: - InputHandler (Protocol).
public protocol InputHandlerProtocol {
var currentLM: LMAssembly.LMInstantiator { get set }
var currentLM: vChewingLM.LMInstantiator { get set }
var currentUOM: vChewingLM.LMUserOverride { get set }
var delegate: InputHandlerDelegate? { get set }
var composer: Tekkon.Composer { get set }
var keySeparator: String { get }
static var keySeparator: String { get }
var isCompositorEmpty: Bool { get }
@ -29,7 +31,7 @@ public protocol InputHandlerProtocol {
func clearComposerAndCalligrapher()
func ensureKeyboardParser()
func triageInput(event input: InputSignalProtocol) -> Bool
func generateStateOfCandidates(dodge: Bool) -> IMEStateProtocol
func generateStateOfCandidates() -> IMEStateProtocol
func generateStateOfInputting(sansReading: Bool, guarded: Bool) -> IMEStateProtocol
func generateStateOfAssociates(withPair pair: Megrez.KeyValuePaired) -> IMEStateProtocol
func consolidateNode(
@ -44,10 +46,6 @@ extension InputHandlerProtocol {
generateStateOfInputting(sansReading: sansReading, guarded: guarded)
}
func generateStateOfCandidates() -> IMEStateProtocol {
generateStateOfCandidates(dodge: true)
}
func consolidateNode(candidate: (keyArray: [String], value: String), respectCursorPushing: Bool, preConsolidate: Bool) {
consolidateNode(
candidate: candidate, respectCursorPushing: respectCursorPushing,
@ -85,20 +83,14 @@ public class InputHandler: InputHandlerProtocol {
public var delegate: InputHandlerDelegate?
public var prefs: PrefMgrProtocol
///
var backupCursor: Int?
///
var currentTypingMethod: TypingMethod = .vChewingFactory
///
let kEpsilon: Double = 0.000_001
var strCodePointBuffer = "" //
var calligrapher = "" //
var composer: Tekkon.Composer = .init() //
var compositor: Megrez.Compositor //
public var currentLM: LMAssembly.LMInstantiator {
public var calligrapher = "" //
public var composer: Tekkon.Composer = .init() //
public var compositor: Megrez.Compositor //
public var currentUOM: vChewingLM.LMUserOverride
public var currentLM: vChewingLM.LMInstantiator {
didSet {
compositor.langModel = .init(withLM: currentLM)
clear()
@ -106,9 +98,10 @@ public class InputHandler: InputHandlerProtocol {
}
///
public init(lm: LMAssembly.LMInstantiator, pref: PrefMgrProtocol) {
public init(lm: vChewingLM.LMInstantiator, uom: vChewingLM.LMUserOverride, pref: PrefMgrProtocol) {
prefs = pref
currentLM = lm
currentUOM = uom
///
Megrez.Compositor.maxSpanLength = prefs.maxCandidateLength
/// ensureCompositor()
@ -120,56 +113,48 @@ public class InputHandler: InputHandlerProtocol {
public func clear() {
clearComposerAndCalligrapher()
compositor.clear()
currentTypingMethod = .vChewingFactory
backupCursor = nil
isCodePointInputMode = false
isHaninKeyboardSymbolMode = false
}
/// /
var isConsideredEmptyForNow: Bool {
compositor.isEmpty && isComposerOrCalligrapherEmpty && currentTypingMethod == .vChewingFactory
compositor.isEmpty && isComposerOrCalligrapherEmpty && !isCodePointInputMode && !isHaninKeyboardSymbolMode
}
// MARK: - Hanin Keyboard Symbol Mode.
var isHaninKeyboardSymbolMode = false
static let tooltipHaninKeyboardSymbolMode: String = "\("Hanin Keyboard Symbol Input.".localized)"
// MARK: - Codepoint Input Buffer.
var isCodePointInputMode = false {
willSet {
strCodePointBuffer.removeAll()
}
}
var strCodePointBuffer = ""
var tooltipCodePointInputMode: String {
let commonTerm = NSMutableString()
commonTerm.insert("Code Point Input.".localized, at: 0)
if !(delegate?.isVerticalTyping ?? false) {
switch IMEApp.currentInputMode {
case .imeModeCHS: commonTerm.insert("[GB] ", at: 0)
case .imeModeCHT: commonTerm.insert("[Big5] ", at: 0)
default: break
}
}
return commonTerm.description
}
// MARK: - Functions dealing with Megrez.
public var isCompositorEmpty: Bool { compositor.isEmpty }
func isInvalidEdgeCursorSituation(givenCursor: Int? = nil) -> Bool {
let cursorToCheck = givenCursor ?? compositor.cursor
// prefs.useRearCursorMode 0 (false) macOS
// prefs.useRearCursorMode 1 (true)
switch prefs.useRearCursorMode {
case false where cursorToCheck == 0: return true
case true where cursorToCheck == compositor.length: return true
default: return false
}
}
public func removeBackupCursor() {
backupCursor = nil
}
public func dodgeInvalidEdgeCursorForCandidateState() {
guard !prefs.useSCPCTypingMode else { return }
guard prefs.dodgeInvalidEdgeCandidateCursorPosition else { return }
guard isInvalidEdgeCursorSituation() else { return }
backupCursor = compositor.cursor
switch prefs.useRearCursorMode {
case false where compositor.cursor < compositor.length:
compositor.cursor += 1
if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .front) }
case true where compositor.cursor > 0:
compositor.cursor -= 1
if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .rear) }
default: break
}
}
public func restoreBackupCursor() {
guard let theBackupCursor = backupCursor else { return }
compositor.cursor = Swift.max(Swift.min(theBackupCursor, compositor.length), 0)
backupCursor = nil
}
///
/// - Returns:
func currentMarkedRange() -> Range<Int> {
@ -213,42 +198,16 @@ public class InputHandler: InputHandlerProtocol {
}
}
///
/// - Parameter pairs:
/// - Returns:
/// nil
func generateArrayOfAssociates(withPairs pairs: [Megrez.KeyValuePaired]) -> [(keyArray: [String], value: String)] {
var arrResult: [(keyArray: [String], value: String)] = []
pairs.forEach { pair in
if currentLM.hasAssociatedPhrasesFor(pair: pair) {
let arrFetched: [String] = currentLM.associatedPhrasesFor(pair: pair)
arrFetched.forEach { thingToAdd in
// keyArray value
if !arrResult.map(\.value).contains(thingToAdd) {
arrResult.append((keyArray: [""], value: thingToAdd))
}
}
}
}
return arrResult
}
///
/// - Parameter pair:
/// - Returns:
///
/// - Parameter key:
/// - Returns:
/// nil
func generateArrayOfAssociates(withPair pair: Megrez.KeyValuePaired) -> [(keyArray: [String], value: String)] {
var pairs = [Megrez.KeyValuePaired]()
var keyArray = pair.keyArray
var value = pair.value
while !keyArray.isEmpty {
// score
if keyArray.count == value.count { pairs.append(.init(keyArray: keyArray, value: value)) }
pairs.append(.init(key: "", value: value)) //
keyArray = Array(keyArray.dropFirst())
value = value.dropFirst().description
var arrResult: [(keyArray: [String], value: String)] = []
if currentLM.hasAssociatedPhrasesFor(pair: pair) {
arrResult = currentLM.associatedPhrasesFor(pair: pair).map { ([""], $0) }
}
return generateArrayOfAssociates(withPairs: pairs)
return arrResult
}
///
@ -366,8 +325,8 @@ public class InputHandler: InputHandlerProtocol {
let currentNode = currentWalk.findNode(at: actualNodeCursorPosition, target: &accumulatedCursor)
guard let currentNode = currentNode else { return }
uomProcessing: if currentNode.currentUnigram.score > -12, prefs.fetchSuggestionsFromUserOverrideModel {
if skipObservation { break uomProcessing }
uom: if currentNode.currentUnigram.score > -12, prefs.fetchSuggestionsFromUserOverrideModel {
if skipObservation { break uom }
vCLog("UOM: Start Observation.")
// 使
//
@ -375,9 +334,9 @@ public class InputHandler: InputHandlerProtocol {
prefs.failureFlagForUOMObservation = true
//
//
currentLM.performUOMObservation(
currentUOM.performObservation(
walkedBefore: previousWalk, walkedAfter: currentWalk, cursor: actualNodeCursorPosition,
timestamp: Date().timeIntervalSince1970
timestamp: Date().timeIntervalSince1970, saveCallback: { self.currentUOM.saveData() }
)
//
prefs.failureFlagForUOMObservation = false
@ -429,7 +388,7 @@ public class InputHandler: InputHandlerProtocol {
///
if !prefs.fetchSuggestionsFromUserOverrideModel { return arrResult }
///
let suggestion = currentLM.fetchUOMSuggestion(
let suggestion = currentUOM.fetchSuggestion(
currentWalk: compositor.walkedNodes, cursor: actualNodeCursorPosition, timestamp: Date().timeIntervalSince1970
)
arrResult.append(contentsOf: suggestion.candidates)
@ -600,7 +559,7 @@ public class InputHandler: InputHandlerProtocol {
if prefs.halfWidthPunctuationEnabled { return "_half_punctuation_" }
// SHIFT+ALT+
// input.isMainAreaNumKey Shift
if input.isMainAreaNumKey, input.commonKeyModifierFlags == [.option, .shift] { return "_shift_alt_punctuation_" }
if input.isMainAreaNumKey, input.keyModifierFlags == [.option, .shift] { return "_shift_alt_punctuation_" }
var result = ""
switch (input.isControlHold, input.isOptionHold) {
case (true, true): result.append("_alt_ctrl_punctuation_")

View File

@ -9,68 +9,22 @@
/// 調
import CandidateWindow
import CocoaExtension
import InputMethodKit
import Megrez
import OSFrameworkImpl
import Shared
// MARK: - § 調 (Handle Candidate State).
extension InputHandler {
///
/// - Parameters:
/// - input:
/// - ignoringModifiers:
/// - Parameter input:
/// - Returns: IMK
func handleCandidate(input: InputSignalProtocol, ignoringModifiers: Bool = false) -> Bool {
func handleCandidate(input: InputSignalProtocol) -> Bool {
guard let delegate = delegate else { return false }
guard var ctlCandidate = delegate.candidateController() else { return false }
let state = delegate.state
guard state.isCandidateContainer else { return false } // isEmpty
guard ctlCandidate.visible else { return false }
let inputText = ignoringModifiers ? (input.inputTextIgnoringModifiers ?? input.text) : input.text
let allowMovingCompositorCursor = state.type == .ofCandidates && !prefs.useSCPCTypingMode
let highlightedCandidate = state.candidates[ctlCandidate.highlightedIndex]
// MARK: Shift+?
var candidateTextServiceMenuRunning: Bool {
state.node.containsCandidateServices && state.type == .ofSymbolTable
}
serviceMenu: if prefs.useShiftQuestionToCallServiceMenu, input.commonKeyModifierFlags == .shift, input.text == "?" {
if candidateTextServiceMenuRunning { break serviceMenu }
let handled = handleServiceMenuInitiation(
candidateText: highlightedCandidate.value,
reading: highlightedCandidate.keyArray
)
if handled { return true }
}
// MARK: / /
if input.isSymbolMenuPhysicalKey {
switch input.commonKeyModifierFlags {
case .shift, [],
.option where !candidateTextServiceMenuRunning:
if !candidateTextServiceMenuRunning {
let handled = handleServiceMenuInitiation(
candidateText: highlightedCandidate.value,
reading: highlightedCandidate.keyArray
)
if handled { return true }
}
var updated = true
let reverseTrigger = input.isShiftHold || input.isOptionHold
updated = reverseTrigger ? ctlCandidate.showPreviousLine() : ctlCandidate.showNextLine()
if !updated { delegate.callError("66F3477B") }
return true
case .option where state.type == .ofSymbolTable:
//
return revolveTypingMethod(to: .haninKeyboardSymbol)
default: break
}
}
// MARK: 使
@ -81,7 +35,7 @@ extension InputHandler {
if candidates[highlightedIndex].keyArray.count < 2 || candidates[highlightedIndex].value.count < 2 {
break manipulator
}
switch input.commonKeyModifierFlags {
switch input.keyModifierFlags {
case [.option, .command] where input.keyCode == 27: //
ctlCandidate.delegate?.candidatePairRightClicked(at: highlightedIndex, action: .toNerf)
return true
@ -103,11 +57,11 @@ extension InputHandler {
// MARK: (Cancel Candidate)
let dismissingCandidateWindow =
let cancelCandidateKey =
input.isBackSpace || input.isEsc || input.isDelete
|| ((input.isCursorBackward || input.isCursorForward) && input.commonKeyModifierFlags == .shift)
|| ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold)
if dismissingCandidateWindow {
if cancelCandidateKey {
if state.type == .ofAssociates
|| prefs.useSCPCTypingMode
|| compositor.isEmpty
@ -119,7 +73,7 @@ extension InputHandler {
delegate.switchState(IMEState.ofAbortion())
} else {
delegate.switchState(generateStateOfInputting())
if input.isCursorBackward || input.isCursorForward, input.commonKeyModifierFlags == .shift {
if input.isCursorBackward || input.isCursorForward, input.isShiftHold {
return triageInput(event: input)
}
}
@ -142,21 +96,7 @@ extension InputHandler {
delegate.switchState(IMEState.ofAbortion())
return true
}
var handleAssociates = !prefs.useSCPCTypingMode && prefs.associatedPhrasesEnabled //
handleAssociates = handleAssociates && compositor.cursor == compositor.length //
confirmHighlightedCandidate()
//
associatedPhrases: if handleAssociates {
guard handleAssociates else { break associatedPhrases }
guard input.commonKeyModifierFlags == .shift else { break associatedPhrases }
let pair = Megrez.KeyValuePaired(
keyArray: highlightedCandidate.keyArray, value: highlightedCandidate.value
)
let associatedCandidates = generateArrayOfAssociates(withPair: pair)
guard !associatedCandidates.isEmpty else { break associatedPhrases }
delegate.switchState(IMEState.ofCommitting(textToCommit: state.displayedText))
delegate.switchState(IMEState.ofAssociates(candidates: associatedCandidates))
}
return true
case .kTab:
let updated: Bool =
@ -191,55 +131,18 @@ extension InputHandler {
_ = ctlCandidate.showPreviousPage() ? {}() : delegate.callError("9569955D")
return true
case .kUpArrow, .kDownArrow, .kLeftArrow, .kRightArrow:
switch input.commonKeyModifierFlags {
case [.option, .shift] where allowMovingCompositorCursor && input.isCursorForward:
if compositor.cursor < compositor.length {
compositor.cursor += 1
if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .front) }
delegate.switchState(generateStateOfCandidates())
} else {
delegate.callError("D3006C85")
}
return true
case [.option, .shift] where allowMovingCompositorCursor && input.isCursorBackward:
if compositor.cursor > 0 {
compositor.cursor -= 1
if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .rear) }
delegate.switchState(generateStateOfCandidates())
} else {
delegate.callError("DE9DAF0D")
}
return true
case .option where input.isCursorForward:
if compositor.cursor < compositor.length {
compositor.jumpCursorBySpan(to: .front)
delegate.switchState(generateStateOfCandidates())
} else {
delegate.callError("5D9F4819")
}
return true
case .option where input.isCursorBackward:
if compositor.cursor > 0 {
compositor.jumpCursorBySpan(to: .rear)
delegate.switchState(generateStateOfCandidates())
} else {
delegate.callError("34B6322D")
}
return true
default:
handleArrowKey: switch (keyCodeType, ctlCandidate.currentLayout) {
case (.kLeftArrow, .horizontal), (.kUpArrow, .vertical): // Previous Candidate
_ = ctlCandidate.highlightPreviousCandidate()
case (.kRightArrow, .horizontal), (.kDownArrow, .vertical): // Next Candidate
_ = ctlCandidate.highlightNextCandidate()
case (.kUpArrow, .horizontal), (.kLeftArrow, .vertical): // Previous Line
_ = ctlCandidate.showPreviousLine()
case (.kDownArrow, .horizontal), (.kRightArrow, .vertical): // Next Line
_ = ctlCandidate.showNextLine()
default: break handleArrowKey
}
return true
handleArrowKey: switch (keyCodeType, ctlCandidate.currentLayout) {
case (.kLeftArrow, .horizontal), (.kUpArrow, .vertical): // Previous Candidate
_ = ctlCandidate.highlightPreviousCandidate()
case (.kRightArrow, .horizontal), (.kDownArrow, .vertical): // Next Candidate
_ = ctlCandidate.highlightNextCandidate()
case (.kUpArrow, .horizontal), (.kLeftArrow, .vertical): // Previous Line
_ = ctlCandidate.showPreviousLine()
case (.kDownArrow, .horizontal), (.kRightArrow, .vertical): // Next Line
_ = ctlCandidate.showNextLine()
default: break handleArrowKey
}
return true
case .kHome:
_ =
(ctlCandidate.highlightedIndex == 0)
@ -255,54 +158,21 @@ extension InputHandler {
}
}
// MARK: J / K
let allowMovingCompositorCursorByJK = allowMovingCompositorCursor && prefs.useJKtoMoveCompositorCursorInCandidateState
checkMovingCompositorCursorByJK: if allowMovingCompositorCursorByJK {
guard input.keyModifierFlags.isEmpty else { break checkMovingCompositorCursorByJK }
// keycode: 38 = J, 40 = K.
switch input.keyCode {
case 38:
if compositor.cursor > 0 {
compositor.cursor -= 1
if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .rear) }
delegate.switchState(generateStateOfCandidates())
} else {
delegate.callError("6F389AE9")
}
return true
case 40:
if compositor.cursor < compositor.length {
compositor.cursor += 1
if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .front) }
delegate.switchState(generateStateOfCandidates())
} else {
delegate.callError("EDBD27F2")
}
return true
default: break checkMovingCompositorCursorByJK
}
}
// MARK: (Associated Phrases)
// MARK: (Associated Phrases)
if state.type == .ofAssociates, !input.isShiftHold { return false }
var index: Int?
var shaltShiftHold = [.ofAssociates].contains(state.type)
if state.type == .ofInputting {
if [.ofInputting].contains(state.type) {
let cassetteShift = currentLM.areCassetteCandidateKeysShiftHeld
shaltShiftHold = shaltShiftHold || cassetteShift
}
let matched: String = (shaltShiftHold ? input.inputTextIgnoringModifiers ?? "" : inputText).lowercased()
// J / K J / K
if !(prefs.useJKtoMoveCompositorCursorInCandidateState && "jk".contains(matched)) {
checkSelectionKey: for keyPair in delegate.selectionKeys.enumerated() {
guard matched == keyPair.element.lowercased() else { continue }
index = Int(keyPair.offset)
break checkSelectionKey
}
let matched: String = shaltShiftHold ? input.inputTextIgnoringModifiers ?? "" : input.text
checkSelectionKey: for keyPair in delegate.selectionKeys.enumerated() {
guard matched.lowercased() == keyPair.element.lowercased() else { continue }
index = Int(keyPair.offset)
break checkSelectionKey
}
//
@ -324,27 +194,27 @@ extension InputHandler {
let punctuationNamePrefix: String = generatePunctuationNamePrefix(withKeyCondition: input)
let parser = currentKeyboardParser
let arrCustomPunctuations: [String] = [
punctuationNamePrefix, parser, inputText,
punctuationNamePrefix, parser, input.text,
]
let customPunctuation: String = arrCustomPunctuations.joined()
///
let arrPunctuations: [String] = [
punctuationNamePrefix, inputText,
punctuationNamePrefix, input.text,
]
let punctuation: String = arrPunctuations.joined()
let isInputValid: Bool =
prefs.cassetteEnabled
? currentLM.isThisCassetteKeyAllowed(key: inputText) : composer.inputValidityCheck(key: input.charCode)
? currentLM.isThisCassetteKeyAllowed(key: input.text) : composer.inputValidityCheck(key: input.charCode)
var shouldAutoSelectCandidate: Bool =
isInputValid || currentLM.hasUnigramsFor(keyArray: [customPunctuation])
|| currentLM.hasUnigramsFor(keyArray: [punctuation])
if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey {
let letter = "_letter_\(inputText)"
let letter = "_letter_\(input.text)"
if currentLM.hasUnigramsFor(keyArray: [letter]) { shouldAutoSelectCandidate = true }
}
@ -355,11 +225,11 @@ extension InputHandler {
}
}
// MARK: Flipping pages by using modified bracket keys (when they are not occupied).
// MARK: - Flipping pages by using modified bracket keys (when they are not occupied).
// Shift+Command+[] Chrome Ctrl
let ctrlCMD: Bool = input.commonKeyModifierFlags == [.control, .command]
let ctrlShiftCMD: Bool = input.commonKeyModifierFlags == [.control, .command, .shift]
let ctrlCMD: Bool = input.keyModifierFlags == [.control, .command]
let ctrlShiftCMD: Bool = input.keyModifierFlags == [.control, .command, .shift]
if ctrlShiftCMD || ctrlCMD {
// JIS US
switch (input.keyCode, IMEApp.isKeyboardJIS) {
@ -373,6 +243,23 @@ extension InputHandler {
}
}
// MARK: - Flipping pages by using symbol menu keys (when they are not occupied).
if input.isSymbolMenuPhysicalKey {
switch input.keyModifierFlags {
case .shift, [],
.option where state.type != .ofSymbolTable:
var updated = true
let reverseTrigger = input.isShiftHold || input.isOptionHold
updated = reverseTrigger ? ctlCandidate.showPreviousLine() : ctlCandidate.showNextLine()
if !updated { delegate.callError("66F3477B") }
return true
case .option where state.type == .ofSymbolTable:
return handleHaninKeyboardSymbolModeToggle()
default: break
}
}
if state.type == .ofInputting { return false } // `%quick`
delegate.callError("172A0F81")

View File

@ -17,29 +17,40 @@ extension InputHandler {
/// - Parameter input:
/// - Returns: IMK
func handleComposition(input: InputSignalProtocol) -> Bool? {
guard let delegate = delegate else { return nil }
//
let hardRequirementMet = !input.text.isEmpty && input.charCode.isPrintable
switch currentTypingMethod {
case .codePoint where hardRequirementMet:
return handleCodePointComposition(input: input)
case .haninKeyboardSymbol where [[], .shift].contains(input.keyModifierFlags):
return handleHaninKeyboardSymbolModeInput(input: input)
case .vChewingFactory where hardRequirementMet && prefs.cassetteEnabled:
guard !input.text.isEmpty, input.charCode.isPrintable else { return nil }
if isCodePointInputMode { return handleCodePointComposition(input: input) }
if prefs.cassetteEnabled {
// `%quick`
var handleQuickCandidate = true
if currentLM.areCassetteCandidateKeysShiftHeld { handleQuickCandidate = input.isShiftHold }
let hasQuickCandidates: Bool = delegate.state.type == .ofInputting && delegate.state.isCandidateContainer
// `%symboldef`
if handleCassetteSymbolTable(input: input) {
return true
} else if hasQuickCandidates, input.text != currentLM.cassetteWildcardKey,
let itim = input.inputTextIgnoringModifiers,
let newEv = (input as? NSEvent)?.reinitiate(characters: itim)
{
// `%quick` `%symboldef`
guard !(handleQuickCandidate && handleCandidate(input: newEv)) else { return true }
} else {
// `%quick`
guard !(hasQuickCandidates && handleQuickCandidate && handleCandidate(input: input)) else { return true }
}
return handleCassetteComposition(input: input)
case .vChewingFactory where hardRequirementMet && !prefs.cassetteEnabled:
return handlePhonabetComposition(input: input)
default: return nil
}
return handlePhonabetComposition(input: input)
}
}
// MARK: - (Handle BPMF Keys)
// MARK: (Handle BPMF Keys)
private extension InputHandler {
/// InputHandler.HandleInput()
/// - Parameter input:
/// - Returns: IMK
func handlePhonabetComposition(input: InputSignalProtocol) -> Bool? {
private func handlePhonabetComposition(input: InputSignalProtocol) -> Bool? {
guard let delegate = delegate else { return nil }
var inputText = (input.inputTextIgnoringModifiers ?? input.text)
inputText = inputText.lowercased().applyingTransformFW2HW(reverse: false)
@ -56,14 +67,6 @@ private extension InputHandler {
|| input.isControlHold || input.isOptionHold || input.isShiftHold || input.isCommandHold
let confirmCombination = input.isSpace || input.isEnter
func narrateTheComposer(with maybeKey: String? = nil, when condition: Bool, allowDuplicates: Bool = true) {
guard condition else { return }
let maybeKey = maybeKey ?? composer.phonabetKeyForQuery(pronounceableOnly: prefs.acceptLeadingIntonations)
guard var keyToNarrate = maybeKey else { return }
if composer.intonation == Tekkon.Phonabet(" ") { keyToNarrate.append("ˉ") }
SpeechSputnik.shared.narrate(keyToNarrate, allowDuplicates: allowDuplicates)
}
// inputValidityCheck() charCode UniChar
// keyConsumedByReading
// composer.receiveKey() String UniChar
@ -97,7 +100,6 @@ private extension InputHandler {
// Enter (CR / LF)
composer.receiveKey(fromString: confirmCombination ? " " : inputText)
keyConsumedByReading = true
narrateTheComposer(when: !overrideHappened && prefs.readingNarrationCoverage >= 2, allowDuplicates: false)
// 調 setInlineDisplayWithCursor() return true
// 調
@ -119,7 +121,7 @@ private extension InputHandler {
return handleEnter(input: input, readingOnly: true)
}
//
let maybeKey = composer.phonabetKeyForQuery(pronounceableOnly: prefs.acceptLeadingIntonations)
let maybeKey = composer.phonabetKeyForQuery(pronouncable: prefs.acceptLeadingIntonations)
guard let readingKey = maybeKey else { break ifComposeReading }
//
if !currentLM.hasUnigramsFor(keyArray: [readingKey]) {
@ -148,8 +150,6 @@ private extension InputHandler {
} else if !compositor.insertKey(readingKey) {
delegate.callError("3CF278C9: 得檢查對應的語言模組的 hasUnigramsFor() 是否有誤判之情形。")
return true
} else {
narrateTheComposer(with: readingKey, when: prefs.readingNarrationCoverage == 1)
}
//
@ -186,12 +186,8 @@ private extension InputHandler {
delegate.switchState(IMEState.ofCommitting(textToCommit: text))
if prefs.associatedPhrasesEnabled {
let associatedCandidates = generateArrayOfAssociates(withPairs: [.init(keyArray: reading, value: text)])
delegate.switchState(
associatedCandidates.isEmpty
? IMEState.ofEmpty()
: IMEState.ofAssociates(candidates: associatedCandidates)
)
let associatedPhrases = generateStateOfAssociates(withPair: .init(keyArray: reading, value: text))
delegate.switchState(associatedPhrases.candidates.isEmpty ? IMEState.ofEmpty() : associatedPhrases)
}
default: break
}
@ -204,7 +200,7 @@ private extension InputHandler {
/// 調
if keyConsumedByReading {
// strict false
if composer.phonabetKeyForQuery(pronounceableOnly: false) == nil {
if composer.phonabetKeyForQuery(pronouncable: false) == nil {
// 調
if !composer.isPinyinMode, input.isSpace,
compositor.insertKey(existedIntonation.value)
@ -233,31 +229,13 @@ private extension InputHandler {
// MARK: -
private extension InputHandler {
/// InputHandler.HandleInput()
extension InputHandler {
/// InputHandler.HandleInput()
/// - Parameter input:
/// - Returns: IMK
func handleCassetteComposition(input: InputSignalProtocol) -> Bool? {
private func handleCassetteComposition(input: InputSignalProtocol) -> Bool? {
guard let delegate = delegate else { return nil }
let state = delegate.state
// `%quick`
var handleQuickCandidate = true
if currentLM.areCassetteCandidateKeysShiftHeld { handleQuickCandidate = input.isShiftHold }
let hasQuickCandidates: Bool = state.type == .ofInputting && state.isCandidateContainer
// `%symboldef`
if handleCassetteSymbolTable(input: input) {
return true
} else if hasQuickCandidates, input.text != currentLM.cassetteWildcardKey {
// `%quick` `%symboldef`
guard !(handleQuickCandidate && handleCandidate(input: input, ignoringModifiers: true)) else { return true }
} else {
// `%quick`
guard !(hasQuickCandidates && handleQuickCandidate && handleCandidate(input: input)) else { return true }
}
//
var wildcardKey: String { currentLM.cassetteWildcardKey } //
let inputText = input.text
let isWildcardKeyInput: Bool = (inputText == wildcardKey && !wildcardKey.isEmpty)
@ -276,7 +254,7 @@ private extension InputHandler {
calligrapher.count >= currentLM.maxCassetteKeyLength || isLongestPossibleKeyFormed
}
prehandling: if !skipStrokeHandling && currentLM.isThisCassetteKeyAllowed(key: inputText) {
prehandling: if !skipStrokeHandling && currentLM.isThisCassetteKeyAllowed(key: inputText) {
if calligrapher.isEmpty, isWildcardKeyInput {
delegate.callError("3606B9C0")
if input.beganWithLetter {
@ -370,24 +348,20 @@ private extension InputHandler {
inputting.textToCommit = textToCommit
delegate.switchState(inputting)
///
///
if prefs.useSCPCTypingMode {
let candidateState: IMEStateProtocol = generateStateOfCandidates()
switch candidateState.candidates.count {
case 2...: delegate.switchState(candidateState)
case 1:
let firstCandidate = candidateState.candidates.first! //
let reading: [String] = firstCandidate.keyArray
let reading: String = firstCandidate.0.joined(separator: compositor.separator)
let text: String = firstCandidate.value
delegate.switchState(IMEState.ofCommitting(textToCommit: text))
if prefs.associatedPhrasesEnabled {
let associatedCandidates = generateArrayOfAssociates(withPairs: [.init(keyArray: reading, value: text)])
delegate.switchState(
associatedCandidates.isEmpty
? IMEState.ofEmpty()
: IMEState.ofAssociates(candidates: associatedCandidates)
)
let associatedPhrases = generateStateOfAssociates(withPair: .init(keyArray: [reading], value: text))
delegate.switchState(associatedPhrases.candidates.isEmpty ? IMEState.ofEmpty() : associatedPhrases)
}
default: break
}
@ -395,17 +369,16 @@ private extension InputHandler {
// SessionCtl IMK
return true
}
return nil
}
}
// MARK: - (Handle Code Point Input)
// MARK: (Handle Code Point Input)
private extension InputHandler {
/// InputHandler.HandleInput()
/// - Parameter input:
/// - Returns: IMK
func handleCodePointComposition(input: InputSignalProtocol) -> Bool? {
private func handleCodePointComposition(input: InputSignalProtocol) -> Bool? {
guard !input.isReservedKey else { return nil }
guard let delegate = delegate, input.text.count == 1 else { return nil }
guard !input.text.compactMap(\.hexDigitValue).isEmpty else {
@ -418,20 +391,27 @@ private extension InputHandler {
strCodePointBuffer.append(input.text)
var updatedState = generateStateOfInputting(guarded: true)
updatedState.tooltipDuration = 0
updatedState.tooltip = TypingMethod.codePoint.getTooltip(vertical: delegate.isVerticalTyping)
updatedState.tooltip = tooltipCodePointInputMode
delegate.switchState(updatedState)
return true
}
let encoding: CFStringEncodings? = {
switch IMEApp.currentInputMode {
case .imeModeCHS: return .GB_18030_2000
case .imeModeCHT: return .big5_HKSCS_1999
default: return nil
}
}()
guard
var char = "\(strCodePointBuffer)\(input.text)"
.parsedAsHexLiteral(encoding: IMEApp.currentInputMode.nonUTFEncoding)?.first?.description
.parsedAsHexLiteral(encoding: encoding)?.first?.description
else {
delegate.callError("D220B880輸入的字碼沒有對應的字元。")
var updatedState = IMEState.ofAbortion()
updatedState.tooltipDuration = 0
updatedState.tooltip = "Invalid Code Point.".localized
delegate.switchState(updatedState)
currentTypingMethod = .codePoint
isCodePointInputMode = true
return true
}
// macOS
@ -439,46 +419,14 @@ private extension InputHandler {
delegate.switchState(IMEState.ofCommitting(textToCommit: char))
var updatedState = generateStateOfInputting(guarded: true)
updatedState.tooltipDuration = 0
updatedState.tooltip = TypingMethod.codePoint.getTooltip(vertical: delegate.isVerticalTyping)
updatedState.tooltip = tooltipCodePointInputMode
delegate.switchState(updatedState)
currentTypingMethod = .codePoint
isCodePointInputMode = true
return true
default:
delegate.switchState(generateStateOfInputting())
currentTypingMethod = .codePoint
isCodePointInputMode = true
return true
}
}
}
// MARK: - Handle Hanin Keyboard Symbol Inputs
private extension InputHandler {
///
/// - Parameters:
/// - input:
/// - Returns: SessionCtl IMK
func handleHaninKeyboardSymbolModeInput(input: InputSignalProtocol) -> Bool {
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
let charText = input.text.lowercased().applyingTransformFW2HW(reverse: false)
guard CandidateNode.mapHaninKeyboardSymbols.keys.contains(charText) else {
return revolveTypingMethod(to: .vChewingFactory)
}
guard
charText.count == 1, let symbols = CandidateNode.queryHaninKeyboardSymbols(char: charText)
else {
delegate.callError("C1A760C7")
return true
}
// commit buffer ESC
let textToCommit = generateStateOfInputting(sansReading: true).displayedText
delegate.switchState(IMEState.ofCommitting(textToCommit: textToCommit))
if symbols.members.count == 1 {
delegate.switchState(IMEState.ofCommitting(textToCommit: symbols.members.map(\.name).joined()))
} else {
delegate.switchState(IMEState.ofSymbolTable(node: symbols))
}
currentTypingMethod = .vChewingFactory // toggle
return true
}
}

View File

@ -25,20 +25,18 @@ extension InputHandler {
/// - Returns:
public func generateStateOfInputting(sansReading: Bool = false, guarded: Bool = false) -> IMEStateProtocol {
if isConsideredEmptyForNow, !guarded { return IMEState.ofAbortion() }
restoreBackupCursor() // Inputting
var segHighlightedAt: Int?
let handleAsCodePointInput = currentTypingMethod == .codePoint && !sansReading
let cpInput = isCodePointInputMode && !sansReading
/// (Update the composing buffer)
/// IMEStateData NSAttributeString
var displayTextSegments: [String] = handleAsCodePointInput
var displayTextSegments: [String] = cpInput
? [strCodePointBuffer]
: compositor.walkedNodes.values
var cursor = handleAsCodePointInput
var cursor = cpInput
? displayTextSegments.joined().count
: convertCursorForDisplay(compositor.cursor)
let cursorSansReading = cursor
//
let reading: String = (sansReading || currentTypingMethod == .codePoint) ? "" : readingForDisplay
let reading: String = (sansReading || isCodePointInputMode) ? "" : readingForDisplay //
if !reading.isEmpty {
var newDisplayTextSegments = [String]()
var temporaryNode = ""
@ -136,15 +134,11 @@ extension InputHandler {
///
/// - Returns:
public func generateStateOfCandidates(dodge: Bool = true) -> IMEStateProtocol {
guard let delegate = delegate else { return IMEState.ofAbortion() }
if dodge, delegate.state.type == .ofInputting {
dodgeInvalidEdgeCursorForCandidateState()
}
public func generateStateOfCandidates() -> IMEStateProtocol {
var result = IMEState.ofCandidates(
candidates: generateArrayOfCandidates(fixOrder: prefs.useFixedCandidateOrderOnSelection),
displayTextSegments: compositor.walkedNodes.values,
cursor: compositor.cursor
cursor: delegate?.state.cursor ?? generateStateOfInputting().cursor
)
if !prefs.useRearCursorMode {
let markerBackup = compositor.marker
@ -155,24 +149,23 @@ extension InputHandler {
return result
}
// MARK: -
// MARK: -
///
///
///
/// generateStateOfAssociates
/// 使
/// 使
/// Core generateArrayOfAssociates
/// String Swift
/// String Swift
/// nil
///
///
///
/// SessionCtl InputHandler generateArrayOfAssociates().
/// - Parameters:
/// - key:
/// - Returns:
/// - key:
/// - Returns:
public func generateStateOfAssociates(withPair pair: Megrez.KeyValuePaired) -> IMEStateProtocol {
IMEState.ofAssociates(candidates: generateArrayOfAssociates(withPair: pair))
IMEState.ofAssociates(
candidates: generateArrayOfAssociates(withPair: pair))
}
// MARK: -
@ -349,48 +342,101 @@ extension InputHandler {
// MARK: - Enter
/// Enter
/// - Parameters:
/// - input:
/// - readingOnly:
/// - associatesData:
/// .ofInputting()
/// - Parameter input:
/// - Returns: SessionCtl IMK
@discardableResult func handleEnter(
input: InputSignalProtocol, readingOnly: Bool = false,
associatesData: @escaping () -> ([(keyArray: [String], value: String)]) = { [] }
) -> Bool {
@discardableResult func handleEnter(input: InputSignalProtocol, readingOnly: Bool = false) -> Bool {
guard let delegate = delegate else { return false }
let state = delegate.state
guard currentTypingMethod == .vChewingFactory else {
return revolveTypingMethod(to: .vChewingFactory)
}
if isHaninKeyboardSymbolMode { return handleHaninKeyboardSymbolModeToggle() }
if isCodePointInputMode { return handleCodePointInputToggle() }
guard state.type == .ofInputting else { return false }
var displayedText = state.displayedText
if input.commonKeyModifierFlags == [.option, .shift] {
if input.keyModifierFlags == [.option, .shift] {
displayedText = displayedText.map(\.description).joined(separator: " ")
} else if readingOnly {
displayedText = commissionByCtrlCommandEnter()
} else if input.isCommandHold, input.isControlHold {
displayedText = input.isOptionHold
? commissionByCtrlOptionCommandEnter(isShiftPressed: input.isShiftHold)
: commissionByCtrlCommandEnter(isShiftPressed: input.isShiftHold)
displayedText =
input.isOptionHold
? commissionByCtrlOptionCommandEnter(isShiftPressed: input.isShiftHold)
: commissionByCtrlCommandEnter(isShiftPressed: input.isShiftHold)
}
delegate.switchState(IMEState.ofCommitting(textToCommit: displayedText))
return true
}
associatedPhrases: if !prefs.useSCPCTypingMode, prefs.associatedPhrasesEnabled {
guard input.commonKeyModifierFlags == .shift else { break associatedPhrases }
guard isComposerOrCalligrapherEmpty else { break associatedPhrases }
let associatedCandidates = associatesData()
guard !associatedCandidates.isEmpty else { break associatedPhrases }
delegate.switchState(IMEState.ofAssociates(candidates: associatedCandidates))
// MARK: - Command+Enter
/// Command+Enter
/// - Parameter isShiftPressed: Shift
/// - Returns: SessionCtl IMK
private func commissionByCtrlCommandEnter(isShiftPressed: Bool = false) -> String {
var displayedText = compositor.keys.joined(separator: "\t")
if compositor.isEmpty {
displayedText = readingForDisplay
}
if !prefs.cassetteEnabled {
if prefs.inlineDumpPinyinInLieuOfZhuyin {
if !compositor.isEmpty {
var arrDisplayedTextElements = [String]()
compositor.keys.forEach { key in
arrDisplayedTextElements.append(Tekkon.restoreToneOneInPhona(target: key)) //
}
displayedText = arrDisplayedTextElements.joined(separator: "\t")
}
displayedText = Tekkon.cnvPhonaToHanyuPinyin(targetJoined: displayedText) //
}
if prefs.showHanyuPinyinInCompositionBuffer {
if compositor.isEmpty {
displayedText = displayedText.replacingOccurrences(of: "1", with: "")
}
}
}
return true
displayedText = displayedText.replacingOccurrences(of: "\t", with: isShiftPressed ? "-" : " ")
return displayedText
}
// MARK: - Command+Option+Enter Ruby
/// Command+Option+Enter Ruby
/// - Parameter isShiftPressed: Shift
/// - Returns: SessionCtl IMK
private func commissionByCtrlOptionCommandEnter(isShiftPressed: Bool = false) -> String {
var composed = ""
compositor.walkedNodes.smashedPairs.forEach { key, value in
var key = key
if !prefs.cassetteEnabled {
key =
prefs.inlineDumpPinyinInLieuOfZhuyin
? Tekkon.restoreToneOneInPhona(target: key) //
: Tekkon.cnvPhonaToTextbookReading(target: key) //
if prefs.inlineDumpPinyinInLieuOfZhuyin {
key = Tekkon.cnvPhonaToHanyuPinyin(targetJoined: key) //
key = Tekkon.cnvHanyuPinyinToTextbookStyle(targetJoined: key) // 調
}
}
key = key.replacingOccurrences(of: "\t", with: " ")
if isShiftPressed {
if !composed.isEmpty { composed += " " }
composed += key.contains("_") ? "??" : key
return
}
//
composed += key.contains("_") ? value : "<ruby>\(value)<rp>(</rp><rt>\(key)</rt><rp>)</rp></ruby>"
}
return composed
}
// MARK: - BackSpace (macOS Delete)
@ -403,61 +449,62 @@ extension InputHandler {
guard let delegate = delegate else { return false }
let state = delegate.state
guard state.type == .ofInputting else {
currentTypingMethod = .vChewingFactory
isCodePointInputMode = false
return false
}
if currentTypingMethod == .codePoint {
if isCodePointInputMode {
if !strCodePointBuffer.isEmpty {
func refreshState() {
var updatedState = generateStateOfInputting(guarded: true)
updatedState.tooltipDuration = 0
updatedState.tooltip = delegate.state.tooltip
updatedState.tooltip = tooltipCodePointInputMode
delegate.switchState(updatedState)
}
strCodePointBuffer = strCodePointBuffer.dropLast(1).description
if input.commonKeyModifierFlags == .option {
return revolveTypingMethod(to: .codePoint)
if input.keyModifierFlags == .option {
strCodePointBuffer.removeAll()
refreshState()
isCodePointInputMode = true
return true
}
if !strCodePointBuffer.isEmpty {
refreshState()
return true
}
}
return revolveTypingMethod(to: .vChewingFactory)
return handleCodePointInputToggle()
}
// macOS Shift+BackSpace
shiftBksp: if input.commonKeyModifierFlags == .shift {
switch prefs.specifyShiftBackSpaceKeyBehavior {
case 0:
if prefs.cassetteEnabled {
guard input.isShiftHold, calligrapher.isEmpty else { break shiftBksp }
guard let prevReading = previousParsableCalligraph else { break shiftBksp }
compositor.dropKey(direction: .rear)
walk() // Walk walk
calligrapher = prevReading
} else {
guard input.isShiftHold, isComposerOrCalligrapherEmpty else { break shiftBksp }
guard let prevReading = previousParsableReading else { break shiftBksp }
// prevReading 調調
compositor.dropKey(direction: .rear)
walk() // Walk walk
prevReading.1.map(\.description).forEach { composer.receiveKey(fromPhonabet: $0) }
}
delegate.switchState(generateStateOfInputting())
return true
case 1:
delegate.switchState(IMEState.ofAbortion())
return true
default: break
shiftBksp: switch prefs.specifyShiftBackSpaceKeyBehavior {
case 0:
if prefs.cassetteEnabled {
guard input.isShiftHold, calligrapher.isEmpty else { break shiftBksp }
guard let prevReading = previousParsableCalligraph else { break shiftBksp }
compositor.dropKey(direction: .rear)
walk() // Walk walk
calligrapher = prevReading
} else {
guard input.isShiftHold, isComposerOrCalligrapherEmpty else { break shiftBksp }
guard let prevReading = previousParsableReading else { break shiftBksp }
// prevReading 調調
compositor.dropKey(direction: .rear)
walk() // Walk walk
prevReading.1.map(\.description).forEach { composer.receiveKey(fromPhonabet: $0) }
}
delegate.switchState(generateStateOfInputting())
return true
case 1:
delegate.switchState(IMEState.ofAbortion())
return true
default: break
}
let steps = getStepsToNearbyNodeBorder(direction: .rear)
var actualSteps = 1
switch input.commonKeyModifierFlags {
switch input.keyModifierFlags {
case .shift:
delegate.switchState(IMEState.ofAbortion())
return true
@ -476,7 +523,7 @@ extension InputHandler {
}
walk()
} else {
_ = input.commonKeyModifierFlags == .option
_ = input.keyModifierFlags == .option
? clearComposerAndCalligrapher()
: letComposerAndCalligrapherDoBackSpace()
}
@ -505,9 +552,8 @@ extension InputHandler {
guard let delegate = delegate else { return false }
let state = delegate.state
guard currentTypingMethod == .vChewingFactory else {
return revolveTypingMethod(to: .vChewingFactory)
}
if isHaninKeyboardSymbolMode { return handleHaninKeyboardSymbolModeToggle() }
if isCodePointInputMode { return handleCodePointInputToggle() }
guard state.type == .ofInputting else { return false }
@ -516,11 +562,11 @@ extension InputHandler {
// macOS PC Delete .function
//
switch input.commonKeyModifierFlags {
case .shift:
switch input.keyModifierFlags {
case _ where input.isShiftHold && !input.isOptionHold && !input.isControlHold:
delegate.switchState(IMEState.ofAbortion())
return true
case .option:
case _ where !input.isShiftHold && input.isOptionHold && !input.isControlHold:
actualSteps = steps
default: break
}
@ -615,9 +661,8 @@ extension InputHandler {
guard let delegate = delegate else { return false }
let state = delegate.state
guard currentTypingMethod == .vChewingFactory else {
return revolveTypingMethod(to: .vChewingFactory)
}
if isHaninKeyboardSymbolMode { return handleHaninKeyboardSymbolModeToggle() }
if isCodePointInputMode { return handleCodePointInputToggle() }
guard state.type == .ofInputting else { return false }
@ -852,6 +897,73 @@ extension InputHandler {
return true
}
// MARK: - CodePoint Input Toggle
@discardableResult func handleCodePointInputToggle() -> Bool {
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
if isCodePointInputMode {
isCodePointInputMode = false
delegate.switchState(IMEState.ofAbortion())
return true
}
var updatedState = generateStateOfInputting(sansReading: true)
delegate.switchState(IMEState.ofCommitting(textToCommit: updatedState.displayedText))
updatedState = generateStateOfInputting(guarded: true)
updatedState.tooltipDuration = 0
updatedState.tooltip = tooltipCodePointInputMode
delegate.switchState(updatedState)
isCodePointInputMode = true
return true
}
// MARK: - Hanin Pallete
@discardableResult func handleHaninKeyboardSymbolModeToggle() -> Bool {
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
if isCodePointInputMode { isCodePointInputMode = false }
if isHaninKeyboardSymbolMode {
isHaninKeyboardSymbolMode = false
delegate.switchState(IMEState.ofAbortion())
return true
}
var updatedState = generateStateOfInputting(sansReading: true)
delegate.switchState(IMEState.ofCommitting(textToCommit: updatedState.displayedText))
updatedState = generateStateOfInputting(guarded: true)
updatedState.tooltipDuration = 0
updatedState.tooltip = Self.tooltipHaninKeyboardSymbolMode
delegate.switchState(updatedState)
isHaninKeyboardSymbolMode = true
return true
}
///
/// - Parameters:
/// - input:
/// - Returns: SessionCtl IMK
func handleHaninKeyboardSymbolModeInput(input: InputSignalProtocol) -> Bool {
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
let charText = input.text.lowercased().applyingTransformFW2HW(reverse: false)
guard CandidateNode.mapHaninKeyboardSymbols.keys.contains(charText) else {
return handleHaninKeyboardSymbolModeToggle()
}
guard
charText.count == 1, let symbols = CandidateNode.queryHaninKeyboardSymbols(char: charText)
else {
delegate.callError("C1A760C7")
return true
}
// commit buffer ESC
let textToCommit = generateStateOfInputting(sansReading: true).displayedText
delegate.switchState(IMEState.ofCommitting(textToCommit: textToCommit))
if symbols.members.count == 1 {
delegate.switchState(IMEState.ofCommitting(textToCommit: symbols.members.map(\.name).joined()))
} else {
delegate.switchState(IMEState.ofSymbolTable(node: symbols))
}
isHaninKeyboardSymbolMode = false // toggle
return true
}
// MARK: - Symbol Menu Input
///
@ -871,7 +983,7 @@ extension InputHandler {
inputting.textToCommit = textToCommit
delegate.switchState(inputting)
//
let newState = generateStateOfCandidates(dodge: false)
let newState = generateStateOfCandidates()
_ = newState.candidates.isEmpty ? delegate.callError("B5127D8A") : delegate.switchState(newState)
} else { //
delegate.callError("17446655")
@ -898,20 +1010,6 @@ extension InputHandler {
}
}
// MARK: - (Service Menu)
func handleServiceMenuInitiation(candidateText: String, reading: [String]) -> Bool {
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
guard !candidateText.isEmpty else { return false }
let rootNode = CandidateTextService.getCurrentServiceMenu(candidate: candidateText, reading: reading)
guard let rootNode = rootNode else { return false }
// commit buffer ESC
let textToCommit = generateStateOfInputting(sansReading: true).displayedText
delegate.switchState(IMEState.ofCommitting(textToCommit: textToCommit))
delegate.switchState(IMEState.ofSymbolTable(node: rootNode))
return true
}
// MARK: - Caps Lock Caps Lock and Alphanumerical mode
/// CapsLock
@ -929,9 +1027,11 @@ extension InputHandler {
delegate.switchState(IMEState.ofEmpty())
// Shift
var shiftCapsLockHandling = input.isUpperCaseASCIILetterKey && delegate.isASCIIMode
shiftCapsLockHandling = shiftCapsLockHandling || handleCapsLock && input.isShiftHold
guard !shiftCapsLockHandling else { return false }
if (input.isUpperCaseASCIILetterKey && delegate.isASCIIMode)
|| (handleCapsLock && input.isShiftHold)
{
return false
}
// Shift
if delegate.isASCIIMode, !handleCapsLock { return false }
@ -960,7 +1060,7 @@ extension InputHandler {
// !input.isFunctionKeyHold
//
let notEmpty = state.hasComposition && !compositor.isEmpty && isComposerOrCalligrapherEmpty
let bannedModifiers: KBEvent.ModifierFlags = [.option, .shift, .command, .control]
let bannedModifiers: NSEvent.ModifierFlags = [.option, .shift, .command, .control]
let noBannedModifiers = bannedModifiers.intersection(input.keyModifierFlags).isEmpty
var triggered = input.isCursorClockLeft || input.isCursorClockRight
triggered = triggered || (input.isSpace && prefs.chooseCandidateUsingSpace)
@ -1027,35 +1127,6 @@ extension InputHandler {
return false
}
// MARK: - NumPad
///
/// - Parameter input:
/// - Returns: IMK
func handleNumPadKeyInput(input: InputSignalProtocol) -> Bool {
guard let delegate = delegate, input.isNumericPadKey else { return false }
let inputText = input.text
guard inputText.count == 1, input.isASCII else { return false }
guard KeyCode(rawValue: input.keyCode) == nil else { return false } //
let behaviorValue = prefs.numPadCharInputBehavior
let fullWidthResult = behaviorValue % 2 != 0 //
triagePrefs: switch (behaviorValue, isConsideredEmptyForNow) {
case (2, _), (3, _), (4, false), (5, false):
currentLM.setOptions { config in
config.numPadFWHWStatus = fullWidthResult
}
if handlePunctuation("_NumPad_\(inputText)") { return true }
default: break triagePrefs // case 0 & 1
}
currentLM.setOptions { config in
config.numPadFWHWStatus = nil
}
delegate.switchState(IMEState.ofEmpty())
let charToCommit = inputText.applyingTransformFW2HW(reverse: fullWidthResult)
delegate.switchState(IMEState.ofCommitting(textToCommit: charToCommit))
return true
}
// MARK: -
func handleCassetteSymbolTable(input: InputSignalProtocol) -> Bool {

Some files were not shown because too many files have changed in this diff Show More