Repo // Massive refactor & renovation.
- This commit is a massive refactor update towards the current vChewing codebase, modernizing the entire project structure. - Put things into packages to boost compile speed. - Also: IMKCandidates // macOS 10.13 High Sierra compatibility. - ctlIME // Remove duplicated setValue().
This commit is contained in:
parent
498ddcc153
commit
9d077a9d49
2
AUTHORS
2
AUTHORS
|
@ -19,7 +19,7 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in
|
|||
- Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen).
|
||||
- Notifier window.
|
||||
- App-style installer (only preserved for developer purposes).
|
||||
- mgrPrefs (userdefaults manager).
|
||||
- PrefMgr (userdefaults manager).
|
||||
- Mengjuei Hsieh:
|
||||
- McBopomofo for macOS 1.x main developer and architect.
|
||||
- The original C++ version of the User Override Module.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Cocoa
|
||||
import IMKUtils
|
||||
import InputMethodKit
|
||||
|
||||
private let kTargetBin = "vChewing"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FolderMonitor",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "FolderMonitor",
|
||||
targets: ["FolderMonitor"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "FolderMonitor",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
# FolderMonitor
|
||||
|
||||
```
|
||||
// (c) 2018 Daniel Galasko
|
||||
// Ref: https://medium.com/over-engineering/monitoring-a-folder-for-changes-in-ios-dc3f8614f902
|
||||
```
|
||||
|
||||
用以監視使用者辭典資料夾內容變化的模組。監視到變化之後,威注音輸入法會自動整理格式、且自動重新載入之。
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class FolderMonitor {
|
||||
public class FolderMonitor {
|
||||
// MARK: Properties
|
||||
|
||||
/// A file descriptor for the monitored directory.
|
||||
|
@ -13,20 +13,20 @@ class FolderMonitor {
|
|||
/// A dispatch source to monitor a file descriptor created from the directory.
|
||||
private var folderMonitorSource: DispatchSourceFileSystemObject?
|
||||
/// URL for the directory being monitored.
|
||||
let url: URL
|
||||
public let url: URL
|
||||
|
||||
var folderDidChange: (() -> Void)?
|
||||
public var folderDidChange: (() -> Void)?
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
init(url: URL) {
|
||||
public init(url: URL) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
// MARK: Monitoring
|
||||
|
||||
/// Listen for changes to the directory (if we are not already).
|
||||
func startMonitoring() {
|
||||
public func startMonitoring() {
|
||||
guard folderMonitorSource == nil, monitoredFolderFileDescriptor == -1 else {
|
||||
return
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class FolderMonitor {
|
|||
}
|
||||
|
||||
/// Stop listening for changes to the directory, if the source has been created.
|
||||
func stopMonitoring() {
|
||||
public func stopMonitoring() {
|
||||
folderMonitorSource?.cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "NSAttributedTextView",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "NSAttributedTextView",
|
||||
targets: ["NSAttributedTextView"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "NSAttributedTextView",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
# NSAttributedTextView
|
||||
|
||||
可以顯示縱排文字的一種 NSView。
|
||||
|
||||
作者是 Fuziki,並未指定任何授權,但有允許說可以任意使用。
|
||||
|
||||
原始版本參見:https://qiita.com/fuziki/items/b31055a69330a3ce55a5
|
||||
|
||||
威注音輸入法倉庫內提供的版本為經過威注音專案修改過的版本,可用於 macOS 應用程式研發。
|
||||
|
|
@ -98,7 +98,7 @@ public class NSAttributedTextView: NSView {
|
|||
]
|
||||
public var text: String? { didSet { ctFrame = nil } }
|
||||
private var ctFrame: CTFrame?
|
||||
private(set) var currentRect: NSRect?
|
||||
public private(set) var currentRect: NSRect?
|
||||
|
||||
@discardableResult public func shrinkFrame() -> NSRect {
|
||||
let attrString: NSAttributedString = {
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "BookmarkManager",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "BookmarkManager",
|
||||
targets: ["BookmarkManager"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "BookmarkManager",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
# BookmarkManager
|
||||
|
||||
```
|
||||
// Ref: https://stackoverflow.com/a/61695824
|
||||
// License: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
```
|
||||
|
||||
該模組為沙箱機制所必需。不然的話,每次重新啟動之後,輸入法都會喪失對前一次設定過的使用者辭典的沙箱存取許可的記錄,然後使用者就得再次手動設定辭典目錄。也有直接實現該功能的方法,但是比較麻煩,所以就用了這個現成的方案。
|
|
@ -1,18 +1,16 @@
|
|||
//
|
||||
// Ref: https://stackoverflow.com/a/61695824
|
||||
// License: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class BookmarkManager {
|
||||
static let shared = BookmarkManager()
|
||||
public class BookmarkManager {
|
||||
public static let shared = BookmarkManager()
|
||||
// Save bookmark for URL. Use this inside the NSOpenPanel `begin` closure
|
||||
func saveBookmark(for url: URL) {
|
||||
public func saveBookmark(for url: URL) {
|
||||
guard let bookmarkDic = getBookmarkData(url: url),
|
||||
let bookmarkURL = getBookmarkURL()
|
||||
else {
|
||||
IME.prtDebugIntel("Error getting data or bookmarkURL")
|
||||
NSLog("Error getting data or bookmarkURL")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -20,15 +18,15 @@ class BookmarkManager {
|
|||
do {
|
||||
let data = try NSKeyedArchiver.archivedData(withRootObject: bookmarkDic, requiringSecureCoding: false)
|
||||
try data.write(to: bookmarkURL)
|
||||
IME.prtDebugIntel("Did save data to url")
|
||||
NSLog("Did save data to url")
|
||||
} catch {
|
||||
IME.prtDebugIntel("Couldn't save bookmarks")
|
||||
NSLog("Couldn't save bookmarks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load bookmarks when your app launch for example
|
||||
func loadBookmarks() {
|
||||
public func loadBookmarks() {
|
||||
guard let url = getBookmarkURL() else {
|
||||
return
|
||||
}
|
||||
|
@ -42,7 +40,7 @@ class BookmarkManager {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
IME.prtDebugIntel("Couldn't load bookmarks")
|
||||
NSLog("Couldn't load bookmarks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,23 +49,23 @@ class BookmarkManager {
|
|||
let restoredUrl: URL?
|
||||
var isStale = false
|
||||
|
||||
IME.prtDebugIntel("Restoring \(key)")
|
||||
NSLog("Restoring \(key)")
|
||||
do {
|
||||
restoredUrl = try URL(
|
||||
resolvingBookmarkData: value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil,
|
||||
bookmarkDataIsStale: &isStale
|
||||
)
|
||||
} catch {
|
||||
IME.prtDebugIntel("Error restoring bookmarks")
|
||||
NSLog("Error restoring bookmarks")
|
||||
restoredUrl = nil
|
||||
}
|
||||
|
||||
if let url = restoredUrl {
|
||||
if isStale {
|
||||
IME.prtDebugIntel("URL is stale")
|
||||
NSLog("URL is stale")
|
||||
} else {
|
||||
if !url.startAccessingSecurityScopedResource() {
|
||||
IME.prtDebugIntel("Couldn't access: \(url.path)")
|
||||
NSLog("Couldn't access: \(url.path)")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ShiftKeyUpChecker",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "ShiftKeyUpChecker",
|
||||
targets: ["ShiftKeyUpChecker"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "ShiftKeyUpChecker",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# ShiftKeyUpChecker
|
||||
|
||||
```
|
||||
// (c) 2022 and onwards Qwertyyb (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
```
|
||||
|
||||
自[業火五筆輸入法](https://github.com/qwertyyb/Fire)承襲的模組,用來判定「Shift 鍵是否有被單獨摁過」。
|
||||
|
||||
該方法並非 Apple 在官方技術文件當中講明的方法,而是業火五筆輸入法的作者首創。
|
||||
|
||||
方法的原理就是連續分析前後兩個 NSEvent。由於只需要接收藉由 IMK 傳入的 NSEvent 而不需要監聽系統全局鍵盤事件,所以沒有資安疑慮。
|
|
@ -2,28 +2,36 @@
|
|||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
|
||||
import Carbon
|
||||
import Cocoa
|
||||
|
||||
extension Date {
|
||||
static func - (lhs: Date, rhs: Date) -> TimeInterval {
|
||||
fileprivate static func - (lhs: Date, rhs: Date) -> TimeInterval {
|
||||
lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate
|
||||
}
|
||||
}
|
||||
|
||||
class ShiftKeyUpChecker {
|
||||
init() {}
|
||||
private static var checkModifier: NSEvent.ModifierFlags { NSEvent.ModifierFlags.shift }
|
||||
private static var checkKeyCode: [UInt16] {
|
||||
mgrPrefs.togglingAlphanumericalModeWithLShift
|
||||
? [KeyCode.kShift.rawValue, KeyCode.kRightShift.rawValue]
|
||||
: [KeyCode.kRightShift.rawValue]
|
||||
public struct ShiftKeyUpChecker {
|
||||
public init(useLShift: Bool) {
|
||||
alsoToggleWithLShift = useLShift
|
||||
}
|
||||
|
||||
private static let delayInterval = 0.3
|
||||
public var alsoToggleWithLShift = false
|
||||
public var lShiftKeyCode: UInt16 = 56
|
||||
public var rShiftKeyCode: UInt16 = 60
|
||||
|
||||
private static var lastTime: Date = .init()
|
||||
private var checkModifier: NSEvent.ModifierFlags { NSEvent.ModifierFlags.shift }
|
||||
private var checkKeyCode: [UInt16] {
|
||||
alsoToggleWithLShift
|
||||
? [lShiftKeyCode, rShiftKeyCode]
|
||||
: [rShiftKeyCode]
|
||||
}
|
||||
|
||||
private static func checkModifierKeyUp(event: NSEvent) -> Bool {
|
||||
private let delayInterval = 0.3
|
||||
|
||||
private var lastTime: Date = .init()
|
||||
|
||||
private mutating func checkModifierKeyUp(event: NSEvent) -> Bool {
|
||||
if event.type == .flagsChanged,
|
||||
event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .init(rawValue: 0),
|
||||
Date() - lastTime <= delayInterval
|
||||
|
@ -35,14 +43,14 @@ class ShiftKeyUpChecker {
|
|||
return false
|
||||
}
|
||||
|
||||
private static func checkModifierKeyDown(event: NSEvent) -> Bool {
|
||||
private mutating func checkModifierKeyDown(event: NSEvent) -> Bool {
|
||||
let isLeftShift = event.modifierFlags.rawValue & UInt(NX_DEVICELSHIFTKEYMASK) != 0
|
||||
let isRightShift = event.modifierFlags.rawValue & UInt(NX_DEVICERSHIFTKEYMASK) != 0
|
||||
print("isLeftShift: \(isLeftShift), isRightShift: \(isRightShift)")
|
||||
let isKeyDown =
|
||||
event.type == .flagsChanged
|
||||
&& checkModifier.contains(event.modifierFlags.intersection(.deviceIndependentFlagsMask))
|
||||
&& ShiftKeyUpChecker.checkKeyCode.contains(event.keyCode)
|
||||
&& checkKeyCode.contains(event.keyCode)
|
||||
if isKeyDown {
|
||||
// modifier keydown event
|
||||
lastTime = Date()
|
||||
|
@ -53,7 +61,7 @@ class ShiftKeyUpChecker {
|
|||
}
|
||||
|
||||
// To confirm that the shift key is "pressed-and-released".
|
||||
public static func check(_ event: NSEvent) -> Bool {
|
||||
ShiftKeyUpChecker.checkModifierKeyUp(event: event) || ShiftKeyUpChecker.checkModifierKeyDown(event: event)
|
||||
public mutating func check(_ event: NSEvent) -> Bool {
|
||||
checkModifierKeyUp(event: event) || checkModifierKeyDown(event: event)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "LineReader",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "LineReader",
|
||||
targets: ["LineReader"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "LineReader",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
# LineReader
|
||||
```
|
||||
// (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
|
||||
```
|
||||
快速讀行掃描器。僅用於威注音的格式整理模組內。
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Preferences",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Preferences",
|
||||
targets: [
|
||||
"Preferences"
|
||||
]
|
||||
)
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Preferences"
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
# Sindresorhus Preferences
|
||||
|
||||
```
|
||||
// (c) 2018 and onwards Sindre Sorhus (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
```
|
||||
|
||||
See its original repository for details: https://github.com/sindresorhus/Preferences/
|
|
@ -104,7 +104,7 @@ struct Localization {
|
|||
*/
|
||||
static subscript(identifier: Identifier) -> String {
|
||||
// Force-unwrapped since all of the involved code is under our control.
|
||||
let localizedDict = Localization.localizedStrings[identifier]!
|
||||
let localizedDict = Self.localizedStrings[identifier]!
|
||||
let defaultLocalizedString = localizedDict["en"]!
|
||||
|
||||
// Iterate through all user-preferred languages until we find one that has a valid language code.
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,26 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "CocoaExtension",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "CocoaExtension",
|
||||
targets: ["CocoaExtension"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../vChewing_IMKUtils")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "CocoaExtension",
|
||||
dependencies: [
|
||||
.product(name: "IMKUtils", package: "vChewing_IMKUtils")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# CocoaExtension
|
||||
|
||||
威注音輸入法針對 Cocoa 的一些功能擴充,使程式維護體驗更佳。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -0,0 +1,96 @@
|
|||
// (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 Cocoa
|
||||
|
||||
// MARK: NSRect Extension
|
||||
|
||||
extension NSRect {
|
||||
public static var seniorTheBeast: NSRect {
|
||||
NSRect(x: 0.0, y: 0.0, width: 0.114, height: 0.514)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Shell Extension
|
||||
|
||||
extension NSApplication {
|
||||
public static func shell(_ command: String) throws -> String {
|
||||
let task = Process()
|
||||
let pipe = Pipe()
|
||||
|
||||
task.standardOutput = pipe
|
||||
task.standardError = pipe
|
||||
task.arguments = ["-c", command]
|
||||
if #available(macOS 10.13, *) {
|
||||
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
|
||||
} else {
|
||||
task.launchPath = "/bin/zsh"
|
||||
}
|
||||
task.standardInput = nil
|
||||
|
||||
if #available(macOS 10.13, *) {
|
||||
try task.run()
|
||||
}
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let output = String(data: data, encoding: .utf8)!
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
extension NSApplication {
|
||||
// MARK: - System Dark Mode Status Detector.
|
||||
|
||||
public static var isDarkMode: Bool {
|
||||
if #unavailable(macOS 10.14) { return false }
|
||||
if #available(macOS 10.15, *) {
|
||||
let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription
|
||||
.lowercased()
|
||||
return appearanceDescription.contains("dark")
|
||||
} else if let appleInterfaceStyle = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") {
|
||||
return appleInterfaceStyle.lowercased().contains("dark")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Tell whether this IME is running with Root privileges.
|
||||
|
||||
public static var isSudoMode: Bool {
|
||||
NSUserName() == "root"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Real Home Dir for Sandboxed Apps
|
||||
|
||||
extension FileManager {
|
||||
public static let realHomeDir = URL(
|
||||
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Trash a file if it exists.
|
||||
|
||||
extension FileManager {
|
||||
@discardableResult public static func trashTargetIfExists(_ path: String) -> Bool {
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: path) {
|
||||
// 塞入垃圾桶
|
||||
try FileManager.default.trashItem(
|
||||
at: URL(fileURLWithPath: path), resultingItemURL: nil
|
||||
)
|
||||
} else {
|
||||
NSLog("Item doesn't exist: \(path)")
|
||||
}
|
||||
} catch let error as NSError {
|
||||
NSLog("Failed from removing this object: \(path) || Error: \(error)")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Cocoa
|
||||
import IMKUtils
|
||||
|
||||
// MARK: - NSEvent Extension - Reconstructors
|
||||
|
||||
|
@ -47,8 +48,8 @@ extension NSEvent {
|
|||
guard isEmacsKey else { return self }
|
||||
let newKeyCode: UInt16 = {
|
||||
switch isVerticalContext {
|
||||
case false: return IME.vChewingEmacsKey.charKeyMapHorizontal[charCode] ?? 0
|
||||
case true: return IME.vChewingEmacsKey.charKeyMapVertical[charCode] ?? 0
|
||||
case false: return EmacsKey.charKeyMapHorizontal[charCode] ?? 0
|
||||
case true: return EmacsKey.charKeyMapVertical[charCode] ?? 0
|
||||
}
|
||||
}()
|
||||
guard newKeyCode != 0 else { return self }
|
||||
|
@ -75,13 +76,12 @@ extension NSEvent {
|
|||
|
||||
// MARK: - NSEvent Extension - InputSignalProtocol
|
||||
|
||||
extension NSEvent: InputSignalProtocol {
|
||||
public var isASCIIModeInput: Bool { ctlInputMethod.isASCIIModeSituation }
|
||||
public var isTypingVertical: Bool { ctlInputMethod.isVerticalTypingSituation }
|
||||
public var text: String { AppleKeyboardConverter.cnvStringApple2ABC(characters ?? "") }
|
||||
extension NSEvent {
|
||||
public var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
|
||||
public var text: String { convertedFromPhonabets() }
|
||||
public var inputTextIgnoringModifiers: String? {
|
||||
guard let charIgnoringModifiers = charactersIgnoringModifiers else { return nil }
|
||||
return AppleKeyboardConverter.cnvStringApple2ABC(charIgnoringModifiers)
|
||||
guard charactersIgnoringModifiers != nil else { return nil }
|
||||
return convertedFromPhonabets(ignoringModifiers: true)
|
||||
}
|
||||
|
||||
public var charCode: UInt16 {
|
||||
|
@ -121,11 +121,6 @@ extension NSEvent: InputSignalProtocol {
|
|||
return code.rawValue != KeyCode.kNone.rawValue
|
||||
}
|
||||
|
||||
public var isCandidateKey: Bool {
|
||||
mgrPrefs.candidateKeys.contains(text)
|
||||
|| mgrPrefs.candidateKeys.contains(inputTextIgnoringModifiers ?? "114514")
|
||||
}
|
||||
|
||||
/// 單獨用 flags 來判定數字小鍵盤輸入的方法已經失效了,所以必須再增補用 KeyCode 判定的方法。
|
||||
public var isNumericPadKey: Bool { arrNumpadKeyCodes.contains(keyCode) }
|
||||
public var isMainAreaNumKey: Bool { arrMainAreaNumKey.contains(keyCode) }
|
||||
|
@ -191,55 +186,6 @@ extension NSEvent: InputSignalProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - InputSignalProtocol
|
||||
|
||||
public protocol InputSignalProtocol {
|
||||
var isASCIIModeInput: Bool { get }
|
||||
var isTypingVertical: Bool { get }
|
||||
var text: String { get }
|
||||
var inputTextIgnoringModifiers: String? { get }
|
||||
var charCode: UInt16 { get }
|
||||
var keyCode: UInt16 { get }
|
||||
var isFlagChanged: Bool { get }
|
||||
var mainAreaNumKeyChar: String? { get }
|
||||
var isASCII: Bool { get }
|
||||
var isInvalid: Bool { get }
|
||||
var isKeyCodeBlacklisted: Bool { get }
|
||||
var isReservedKey: Bool { get }
|
||||
var isCandidateKey: Bool { get }
|
||||
var isNumericPadKey: Bool { get }
|
||||
var isMainAreaNumKey: Bool { get }
|
||||
var isShiftHold: Bool { get }
|
||||
var isCommandHold: Bool { get }
|
||||
var isControlHold: Bool { get }
|
||||
var isControlHotKey: Bool { get }
|
||||
var isOptionHold: Bool { get }
|
||||
var isOptionHotKey: Bool { get }
|
||||
var isCapsLockOn: Bool { get }
|
||||
var isFunctionKeyHold: Bool { get }
|
||||
var isNonLaptopFunctionKey: Bool { get }
|
||||
var isEnter: Bool { get }
|
||||
var isTab: Bool { get }
|
||||
var isUp: Bool { get }
|
||||
var isDown: Bool { get }
|
||||
var isLeft: Bool { get }
|
||||
var isRight: Bool { get }
|
||||
var isPageUp: Bool { get }
|
||||
var isPageDown: Bool { get }
|
||||
var isSpace: Bool { get }
|
||||
var isBackSpace: Bool { get }
|
||||
var isEsc: Bool { get }
|
||||
var isHome: Bool { get }
|
||||
var isEnd: Bool { get }
|
||||
var isDelete: Bool { get }
|
||||
var isCursorBackward: Bool { get }
|
||||
var isCursorForward: Bool { get }
|
||||
var isCursorClockRight: Bool { get }
|
||||
var isCursorClockLeft: Bool { get }
|
||||
var isUpperCaseASCIILetterKey: Bool { get }
|
||||
var isSymbolMenuPhysicalKey: Bool { get }
|
||||
}
|
||||
|
||||
// MARK: - Enums of Constants
|
||||
|
||||
// Use KeyCodes as much as possible since its recognition won't be affected by macOS Base Keyboard Layouts.
|
||||
|
@ -350,3 +296,10 @@ enum CharCode: UInt16 {
|
|||
// 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 {
|
||||
static let charKeyMapHorizontal: [UInt16: UInt16] = [6: 124, 2: 123, 1: 115, 5: 119, 4: 117, 22: 121]
|
||||
static let charKeyMapVertical: [UInt16: UInt16] = [6: 125, 2: 126, 1: 115, 5: 119, 4: 117, 22: 121]
|
||||
}
|
|
@ -7,32 +7,24 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import AVFoundation
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
public enum clsSFX {
|
||||
static func beep() {
|
||||
let filePath = Bundle.main.path(forResource: mgrPrefs.shouldNotFartInLieuOfBeep ? "Beep" : "Fart", ofType: "m4a")!
|
||||
extension NSSound {
|
||||
public static func buzz(fart: Bool = false) {
|
||||
let filePath = Bundle.main.path(forResource: fart ? "Fart" : "Beep", ofType: "m4a")!
|
||||
let fileURL = URL(fileURLWithPath: filePath)
|
||||
var soundID: SystemSoundID = 0
|
||||
AudioServicesCreateSystemSoundID(fileURL as CFURL, &soundID)
|
||||
AudioServicesPlaySystemSound(soundID)
|
||||
}
|
||||
|
||||
static func fart() {
|
||||
let filePath = Bundle.main.path(forResource: "Fart", ofType: "m4a")!
|
||||
let fileURL = URL(fileURLWithPath: filePath)
|
||||
var soundID: SystemSoundID = 0
|
||||
AudioServicesCreateSystemSoundID(fileURL as CFURL, &soundID)
|
||||
AudioServicesPlaySystemSound(soundID)
|
||||
}
|
||||
|
||||
static func beep(count: Int = 1) {
|
||||
public static func buzz(fart _: Bool = false, count: Int) {
|
||||
if count <= 1 {
|
||||
clsSFX.beep()
|
||||
NSSound.buzz()
|
||||
return
|
||||
}
|
||||
for _ in 0...count {
|
||||
clsSFX.beep()
|
||||
NSSound.buzz()
|
||||
usleep(500_000)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,26 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Hotenka",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Hotenka",
|
||||
targets: ["Hotenka"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Hotenka",
|
||||
dependencies: []
|
||||
),
|
||||
.testTarget(
|
||||
name: "HotenkaTests",
|
||||
dependencies: ["Hotenka"]
|
||||
),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
# Hotenka Engine 步天歌引擎
|
||||
|
||||
- Gitee: [Swift](https://gitee.com/vChewing/Hotenka)
|
||||
- GitHub: [Swift](https://github.com/vChewing/Hotenka)
|
||||
|
||||
步天歌引擎是一套簡繁轉換模組,將 Nick Chen 的 ObjC 模組「[NCChineseConverter](https://github.com/nickcheng/NCChineseConverter)」用 Swift 重寫而得。簡繁轉換資料改用 OpenCC 的轉換資料(Apache License 2.0)且有做了一些修改。
|
||||
|
||||
Hotenka Engine is a module made for converting between Simplified Chinese and Traditional Chinese. This module is using the translation data from OpenCC (Apache License 2.0).
|
||||
|
||||
## 使用說明
|
||||
|
||||
詳見 HotenkaTest.swift。要編譯 plist 詞庫的話,跑一遍單元測試即可自動生成 plist 詞庫檔案。
|
||||
|
||||
## 著作權 (Credits)
|
||||
|
||||
- Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
|
||||
- Swift programmer: Shiki Suen
|
||||
- Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (MIT License).
|
|
@ -1,11 +1,27 @@
|
|||
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
|
||||
// Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (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.
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
|
@ -25,7 +41,7 @@ public enum DictType {
|
|||
}
|
||||
|
||||
public class HotenkaChineseConverter {
|
||||
private(set) var dict: [String: [String: String]]
|
||||
public private(set) var dict: [String: [String: String]]
|
||||
private var dictFiles: [String: [String]]
|
||||
|
||||
public init(plistDir: String) {
|
||||
|
@ -107,7 +123,7 @@ public class HotenkaChineseConverter {
|
|||
|
||||
// MARK: - Public Methods
|
||||
|
||||
func convert(_ target: String, to dictType: DictType) -> String {
|
||||
public func convert(_ target: String, to dictType: DictType) -> String {
|
||||
var dictTypeKey: String
|
||||
|
||||
switch dictType {
|
|
@ -0,0 +1,64 @@
|
|||
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
|
||||
// Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import Hotenka
|
||||
|
||||
private let packageRootPath = URL(fileURLWithPath: #file).pathComponents.prefix(while: { $0 != "Tests" }).joined(
|
||||
separator: "/"
|
||||
).dropFirst()
|
||||
|
||||
private let testDataPath: String = packageRootPath + "/Tests/TestDictData/"
|
||||
|
||||
final class HotenkaTests: XCTestCase {
|
||||
func testGeneratingPlist() throws {
|
||||
NSLog("// Start loading from: \(packageRootPath)")
|
||||
let testInstance: HotenkaChineseConverter = .init(dictDir: testDataPath)
|
||||
NSLog("// Loading complete. Generating plist dict file.")
|
||||
do {
|
||||
try PropertyListSerialization.data(fromPropertyList: testInstance.dict, format: .binary, options: 0).write(
|
||||
to: URL(fileURLWithPath: testDataPath + "convdict.plist"))
|
||||
} catch {
|
||||
NSLog("// Error on writing strings to file: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testSampleWithPlist() throws {
|
||||
NSLog("// Start loading plist from: \(packageRootPath)")
|
||||
let testInstance2: HotenkaChineseConverter = .init(plistDir: testDataPath + "convdict.plist")
|
||||
NSLog("// Successfully loading plist dictionary.")
|
||||
|
||||
let oriString = "为中华崛起而读书"
|
||||
let result1 = testInstance2.convert(oriString, to: .zhHantTW)
|
||||
let result2 = testInstance2.convert(result1, to: .zhHantKX)
|
||||
let result3 = testInstance2.convert(result2, to: .zhHansJP)
|
||||
NSLog("// Results: \(result1) \(result2) \(result3)")
|
||||
XCTAssertEqual(result1, "為中華崛起而讀書")
|
||||
XCTAssertEqual(result2, "爲中華崛起而讀書")
|
||||
XCTAssertEqual(result3, "為中華崛起而読書")
|
||||
}
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,549 @@
|
|||
一攫千金 一獲千金
|
||||
叮嚀 丁寧
|
||||
鄭重 丁重
|
||||
三叉路 三差路
|
||||
輿論 世論
|
||||
啞鈴 亜鈴
|
||||
交叉 交差
|
||||
饗宴 供宴
|
||||
駿馬 俊馬
|
||||
堡壘 保塁
|
||||
箇条書 個条書
|
||||
扁平 偏平
|
||||
碇泊 停泊
|
||||
優駿 優俊
|
||||
尖兵 先兵
|
||||
尖鋭 先鋭
|
||||
共軛 共役
|
||||
饒舌 冗舌
|
||||
兇器 凶器
|
||||
鑿岩 削岩
|
||||
庖丁 包丁
|
||||
繃帯 包帯
|
||||
區劃 区画
|
||||
儼然 厳然
|
||||
友誼 友宜
|
||||
叛乱 反乱
|
||||
蒐集 収集
|
||||
抒情 叙情
|
||||
擡頭 台頭
|
||||
合辦 合弁
|
||||
嬉遊曲 喜遊曲
|
||||
歎願 嘆願
|
||||
廻転 回転
|
||||
回游 回遊
|
||||
捧持 奉持
|
||||
萎縮 委縮
|
||||
輾轉 展転
|
||||
稀少 希少
|
||||
眩惑 幻惑
|
||||
廣汎 広範
|
||||
曠野 広野
|
||||
廢墟 廃虚
|
||||
建蔽率 建坪率
|
||||
辨當 弁当
|
||||
瓣膜 弁膜
|
||||
辯護 弁護
|
||||
辮髮 弁髪
|
||||
絃歌 弦歌
|
||||
恩誼 恩義
|
||||
意嚮 意向
|
||||
慰藉料 慰謝料
|
||||
臆断 憶断
|
||||
臆病 憶病
|
||||
戰歿 戦没
|
||||
煽情 扇情
|
||||
手帖 手帳
|
||||
伎倆 技量
|
||||
抜萃 抜粋
|
||||
披瀝 披歴
|
||||
牴触 抵触
|
||||
抽籤 抽選
|
||||
勾引 拘引
|
||||
醵出 拠出
|
||||
醵金 拠金
|
||||
掘鑿 掘削
|
||||
扣除 控除
|
||||
掩護 援護
|
||||
抛棄 放棄
|
||||
撒水 散水
|
||||
敬虔 敬謙
|
||||
敷衍 敷延
|
||||
断乎 断固
|
||||
簇生 族生
|
||||
陞敘 昇叙
|
||||
煖房 暖房
|
||||
暗誦 暗唱
|
||||
闇夜 暗夜
|
||||
曝露 暴露
|
||||
涸渇 枯渇
|
||||
恰好 格好
|
||||
恰幅 格幅
|
||||
毀損 棄損
|
||||
摸索 模索
|
||||
橋頭堡 橋頭保
|
||||
欠缺 欠缺
|
||||
屍體 死体
|
||||
臀部 殿部
|
||||
拇指 母指
|
||||
気魄 気迫
|
||||
訣別 決別
|
||||
決潰 決壊
|
||||
沈澱 沈殿
|
||||
油槽船 油送船
|
||||
波瀾 波乱
|
||||
註釋 注釈
|
||||
洗滌 洗浄 洗浄
|
||||
活潑 活発
|
||||
滲透 浸透
|
||||
浸蝕 浸食
|
||||
銷卻 消却
|
||||
渾然 混然
|
||||
彎曲 湾曲
|
||||
熔接 溶接
|
||||
漁撈 漁労
|
||||
飄然 漂然
|
||||
激昂 激高
|
||||
火焰 火炎
|
||||
焦躁 焦燥
|
||||
斑点 班点
|
||||
溜飲 留飲
|
||||
掠奪 略奪
|
||||
疏通 疎通
|
||||
醱酵 発酵
|
||||
白堊 白亜
|
||||
相剋 相克
|
||||
智慧 知恵
|
||||
破毀 破棄
|
||||
確乎 確固
|
||||
禁錮 禁固
|
||||
符牒 符丁
|
||||
扮装 粉装
|
||||
紫斑 紫班
|
||||
終熄 終息
|
||||
綜合 総合
|
||||
編輯 編集
|
||||
義捐 義援
|
||||
耕耘機 耕運機
|
||||
肝腎 肝心
|
||||
肩胛骨 肩甲骨
|
||||
悖德 背徳
|
||||
脈搏 脈拍
|
||||
膨脹 膨張
|
||||
芳醇 芳純
|
||||
叡智 英知
|
||||
蒸溜 蒸留
|
||||
燻蒸 薫蒸
|
||||
燻製 薫製
|
||||
衣裳 衣装
|
||||
衰退 衰頽 衰退
|
||||
悠然 裕然
|
||||
輔佐 補佐
|
||||
訓誡 訓戒
|
||||
試煉 試練
|
||||
詭辯 詭弁
|
||||
媾和 講和
|
||||
象嵌 象眼
|
||||
貫禄 貫録
|
||||
買辦 買弁
|
||||
讚辭 賛辞
|
||||
蹈襲 踏襲
|
||||
車輛 車両
|
||||
顛倒 転倒
|
||||
輪廓 輪郭
|
||||
褪色 退色
|
||||
杜絶 途絶
|
||||
連繫 連係
|
||||
聯合 連合
|
||||
銓衡 選考
|
||||
醋酸 酢酸
|
||||
野鄙 野卑
|
||||
礦石 鉱石
|
||||
間歇 間欠
|
||||
函數 関数
|
||||
防禦 防御
|
||||
嶮岨 険阻
|
||||
牆壁 障壁
|
||||
障礙 障害
|
||||
湮滅 隠滅
|
||||
聚落 集落
|
||||
雇傭 雇用
|
||||
諷喩 風諭
|
||||
蜚語 飛語
|
||||
香奠 香典
|
||||
骨骼 骨格
|
||||
亢進 高進
|
||||
鳥瞰 鳥観
|
||||
兩 両
|
||||
輛 両
|
||||
辨 弁
|
||||
辯 弁
|
||||
瓣 弁
|
||||
辦 弁
|
||||
禦 御
|
||||
缺 欠
|
||||
絲 糸
|
||||
藝 芸
|
||||
濱 浜
|
||||
乘 乗
|
||||
亂 乱
|
||||
亙 亘
|
||||
亞 亜
|
||||
佛 仏
|
||||
來 来
|
||||
假 仮
|
||||
傳 伝
|
||||
僞 偽
|
||||
價 価
|
||||
儉 倹
|
||||
兒 児
|
||||
內 内
|
||||
剎 刹
|
||||
剩 剰
|
||||
劍 剣
|
||||
剱 剣
|
||||
劎 剣
|
||||
劒 剣
|
||||
劔 剣
|
||||
劑 剤
|
||||
勞 労
|
||||
勳 勲
|
||||
勵 励
|
||||
勸 勧
|
||||
勻 匀
|
||||
區 区
|
||||
卷 巻
|
||||
卻 却
|
||||
參 参
|
||||
吳 呉
|
||||
咒 呪
|
||||
啞 唖
|
||||
單 単
|
||||
噓 嘘
|
||||
嚙 噛
|
||||
嚴 厳
|
||||
囑 嘱
|
||||
圈 圏
|
||||
國 国
|
||||
圍 囲
|
||||
圓 円
|
||||
圖 図
|
||||
團 団
|
||||
增 増
|
||||
墮 堕
|
||||
壓 圧
|
||||
壘 塁
|
||||
壞 壊
|
||||
壤 壌
|
||||
壯 壮
|
||||
壹 壱
|
||||
壽 寿
|
||||
奧 奥
|
||||
奬 奨
|
||||
妝 粧
|
||||
孃 嬢
|
||||
學 学
|
||||
寢 寝
|
||||
實 実
|
||||
寫 写
|
||||
寬 寛
|
||||
寶 宝
|
||||
將 将
|
||||
專 専
|
||||
對 対
|
||||
屆 届
|
||||
屬 属
|
||||
峯 峰
|
||||
峽 峡
|
||||
嶽 岳
|
||||
巖 巌
|
||||
巢 巣
|
||||
帶 帯
|
||||
廁 厠
|
||||
廢 廃
|
||||
廣 広
|
||||
廳 庁
|
||||
彈 弾
|
||||
彌 弥
|
||||
彎 弯
|
||||
彥 彦
|
||||
徑 径
|
||||
從 従
|
||||
徵 徴
|
||||
德 徳
|
||||
恆 恒
|
||||
悅 悦
|
||||
惠 恵
|
||||
惡 悪
|
||||
惱 悩
|
||||
慘 惨
|
||||
應 応
|
||||
懷 懐
|
||||
戀 恋
|
||||
戰 戦
|
||||
戲 戯
|
||||
戶 戸
|
||||
戾 戻
|
||||
拂 払
|
||||
拔 抜
|
||||
拜 拝
|
||||
挾 挟
|
||||
插 挿
|
||||
揭 掲
|
||||
搔 掻
|
||||
搖 揺
|
||||
搜 捜
|
||||
摑 掴
|
||||
擇 択
|
||||
擊 撃
|
||||
擔 担
|
||||
據 拠
|
||||
擴 拡
|
||||
攝 摂
|
||||
攪 撹
|
||||
收 収
|
||||
效 効
|
||||
敕 勅
|
||||
敘 叙
|
||||
數 数
|
||||
斷 断
|
||||
晉 晋
|
||||
晚 晩
|
||||
晝 昼
|
||||
暨 曁
|
||||
曆 暦
|
||||
曉 暁
|
||||
曾 曽
|
||||
會 会
|
||||
枡 桝
|
||||
查 査
|
||||
條 条
|
||||
棧 桟
|
||||
棱 稜
|
||||
榆 楡
|
||||
榮 栄
|
||||
樂 楽
|
||||
樓 楼
|
||||
樞 枢
|
||||
樣 様
|
||||
橫 横
|
||||
檢 検
|
||||
櫻 桜
|
||||
權 権
|
||||
歐 欧
|
||||
歡 歓
|
||||
步 歩
|
||||
歲 歳
|
||||
歷 歴
|
||||
歸 帰
|
||||
殘 残
|
||||
殼 殻
|
||||
毆 殴
|
||||
每 毎
|
||||
氣 気
|
||||
污 汚
|
||||
沒 没
|
||||
涉 渉
|
||||
淚 涙
|
||||
淨 浄
|
||||
淺 浅
|
||||
渴 渇
|
||||
溌 潑
|
||||
溪 渓
|
||||
溫 温
|
||||
溼 湿
|
||||
滯 滞
|
||||
滿 満
|
||||
潛 潜
|
||||
澀 渋
|
||||
澤 沢
|
||||
濟 済
|
||||
濤 涛
|
||||
濾 沪
|
||||
瀧 滝
|
||||
瀨 瀬
|
||||
灣 湾
|
||||
焰 焔
|
||||
燈 灯
|
||||
燒 焼
|
||||
營 営
|
||||
爐 炉
|
||||
爭 争
|
||||
爲 為
|
||||
牀 床
|
||||
犧 犠
|
||||
狀 状
|
||||
狹 狭
|
||||
獨 独
|
||||
獵 猟
|
||||
獸 獣
|
||||
獻 献
|
||||
產 産
|
||||
畫 画
|
||||
當 当
|
||||
疊 畳
|
||||
疎 疏
|
||||
痹 痺
|
||||
瘦 痩
|
||||
癡 痴
|
||||
發 発
|
||||
皋 皐
|
||||
盜 盗
|
||||
盡 尽
|
||||
碎 砕
|
||||
祕 秘
|
||||
祿 禄
|
||||
禪 禅
|
||||
禮 礼
|
||||
禱 祷
|
||||
稅 税
|
||||
稱 称
|
||||
稻 稲
|
||||
穎 頴
|
||||
穗 穂
|
||||
穩 穏
|
||||
穰 穣
|
||||
竃 竈
|
||||
竊 窃
|
||||
粹 粋
|
||||
糉 粽
|
||||
絕 絶
|
||||
經 経
|
||||
綠 緑
|
||||
緖 緒
|
||||
緣 縁
|
||||
縣 県
|
||||
縱 縦
|
||||
總 総
|
||||
繋 繫
|
||||
繡 繍
|
||||
繩 縄
|
||||
繪 絵
|
||||
繼 継
|
||||
續 続
|
||||
纔 才
|
||||
纖 繊
|
||||
罐 缶
|
||||
羣 群
|
||||
聯 連
|
||||
聰 聡
|
||||
聲 声
|
||||
聽 聴
|
||||
肅 粛
|
||||
脣 唇
|
||||
脫 脱
|
||||
腦 脳
|
||||
腳 脚
|
||||
膽 胆
|
||||
臟 臓
|
||||
臺 台
|
||||
與 与
|
||||
舉 挙
|
||||
舊 旧
|
||||
舍 舎
|
||||
荔 茘
|
||||
莊 荘
|
||||
莖 茎
|
||||
菸 煙
|
||||
萊 莱
|
||||
萬 万
|
||||
蔣 蒋
|
||||
蔥 葱
|
||||
薰 薫
|
||||
藏 蔵
|
||||
藥 薬
|
||||
蘆 芦
|
||||
處 処
|
||||
虛 虚
|
||||
號 号
|
||||
螢 蛍
|
||||
蟲 虫
|
||||
蠟 蝋
|
||||
蠶 蚕
|
||||
蠻 蛮
|
||||
裝 装
|
||||
覺 覚
|
||||
覽 覧
|
||||
觀 観
|
||||
觸 触
|
||||
說 説
|
||||
謠 謡
|
||||
證 証
|
||||
譯 訳
|
||||
譽 誉
|
||||
讀 読
|
||||
變 変
|
||||
讓 譲
|
||||
豐 豊
|
||||
豫 予
|
||||
貓 猫
|
||||
貳 弐
|
||||
賣 売
|
||||
賴 頼
|
||||
贊 賛
|
||||
贗 贋
|
||||
踐 践
|
||||
輕 軽
|
||||
輛 輌
|
||||
轉 転
|
||||
辭 辞
|
||||
遞 逓
|
||||
遥 遙
|
||||
遲 遅
|
||||
邊 辺
|
||||
鄉 郷
|
||||
酢 醋
|
||||
醉 酔
|
||||
醗 醱
|
||||
醫 医
|
||||
醬 醤
|
||||
釀 醸
|
||||
釋 釈
|
||||
鋪 舗
|
||||
錄 録
|
||||
錢 銭
|
||||
鍊 錬
|
||||
鐵 鉄
|
||||
鑄 鋳
|
||||
鑛 鉱
|
||||
閱 閲
|
||||
關 関
|
||||
陷 陥
|
||||
隨 随
|
||||
險 険
|
||||
隱 隠
|
||||
雙 双
|
||||
雜 雑
|
||||
雞 鶏
|
||||
霸 覇
|
||||
靈 霊
|
||||
靜 静
|
||||
顏 顔
|
||||
顯 顕
|
||||
餘 余
|
||||
騷 騒
|
||||
驅 駆
|
||||
驗 験
|
||||
驛 駅
|
||||
髓 髄
|
||||
體 体
|
||||
髮 髪
|
||||
鬥 闘
|
||||
鱉 鼈
|
||||
鷗 鴎
|
||||
鹼 鹸
|
||||
鹽 塩
|
||||
麥 麦
|
||||
麪 麺
|
||||
麴 麹
|
||||
黃 黄
|
||||
黑 黒
|
||||
默 黙
|
||||
點 点
|
||||
黨 党
|
||||
齊 斉
|
||||
齋 斎
|
||||
齒 歯
|
||||
齡 齢
|
||||
龍 竜
|
||||
龜 亀
|
|
@ -0,0 +1,107 @@
|
|||
一口吃個 一口喫個
|
||||
一口吃成 一口喫成
|
||||
一家三口 一家三口
|
||||
一家五口 一家五口
|
||||
一家六口 一家六口
|
||||
一家四口 一家四口
|
||||
凶事 凶事
|
||||
凶信 凶信
|
||||
凶兆 凶兆
|
||||
凶吉 凶吉
|
||||
凶地 凶地
|
||||
凶多吉少 凶多吉少
|
||||
凶宅 凶宅
|
||||
凶年 凶年
|
||||
凶德 凶德
|
||||
凶怪 凶怪
|
||||
凶日 凶日
|
||||
凶服 凶服
|
||||
凶歲 凶歲
|
||||
凶死 凶死
|
||||
凶氣 凶氣
|
||||
凶煞 凶煞
|
||||
凶燄 凶燄
|
||||
凶神 凶神
|
||||
凶禮 凶禮
|
||||
凶耗 凶耗
|
||||
凶肆 凶肆
|
||||
凶荒 凶荒
|
||||
凶訊 凶訊
|
||||
凶豎 凶豎
|
||||
凶身 凶身
|
||||
凶逆 凶逆
|
||||
凶門 凶門
|
||||
口吃 口吃
|
||||
吃口 喫口 吃口
|
||||
吃口令 吃口令
|
||||
吃口飯 喫口飯
|
||||
吃吃 喫喫 吃吃
|
||||
吃子 喫子 吃子
|
||||
合著 合著
|
||||
吉凶 吉凶
|
||||
名著 名著
|
||||
四凶 四凶
|
||||
大凶 大凶
|
||||
巨著 巨著
|
||||
張口 張口
|
||||
昭著 昭著
|
||||
歲凶 歲凶
|
||||
胃口 胃口
|
||||
著作 著作
|
||||
著名 著名
|
||||
著式 著式
|
||||
著志 著志
|
||||
著於 著於
|
||||
著書 著書
|
||||
著白 著白
|
||||
著稱 著稱
|
||||
著者 著者
|
||||
著述 著述
|
||||
著錄 著錄
|
||||
蹇吃 蹇吃
|
||||
逢凶 逢凶
|
||||
避凶 避凶
|
||||
鄧艾吃 鄧艾吃
|
||||
鉅著 鉅著
|
||||
開口 開口
|
||||
閔凶 閔凶
|
||||
顯著 顯著
|
||||
偽 僞
|
||||
啟 啓
|
||||
吃 喫
|
||||
嫻 嫺
|
||||
媯 嬀
|
||||
峰 峯
|
||||
么 幺
|
||||
抬 擡
|
||||
稜 棱
|
||||
簷 檐
|
||||
汙 污
|
||||
洩 泄
|
||||
溈 潙
|
||||
潀 潨
|
||||
為 爲
|
||||
床 牀
|
||||
痺 痹
|
||||
痴 癡
|
||||
皂 皁
|
||||
著 着
|
||||
睪 睾
|
||||
秘 祕
|
||||
灶 竈
|
||||
粽 糉
|
||||
韁 繮
|
||||
才 纔
|
||||
群 羣
|
||||
唇 脣
|
||||
參 蔘
|
||||
蒍 蔿
|
||||
眾 衆
|
||||
裡 裏
|
||||
核 覈
|
||||
踴 踊
|
||||
缽 鉢
|
||||
針 鍼
|
||||
鯰 鮎
|
||||
麵 麪
|
||||
顎 齶
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
.PHONY: format
|
||||
|
||||
format:
|
||||
swiftformat ./ --swiftversion 5.5
|
||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format format --in-place --configuration ./.clang-format-swift.json --parallel
|
||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "IMKUtils",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "IMKUtils",
|
||||
targets: ["IMKUtils"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "IMKUtils",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
# IMKUtils
|
||||
|
||||
該模組套裝使得輸入法的登記啟用過程與鍵盤佈局管理過程更加簡便。分兩部分組成:
|
||||
|
||||
- IMKHelper: (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
- TISInputSourceExtension:
|
||||
- (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
- (c) 2018 and onwards Mizuno Hiroki (a.k.a. "Mzp") (MIT License).
|
||||
- 詳情請參見 TISInputSourceExtension.swift 內部的各種 pragma mark 標記。
|
|
@ -6,16 +6,110 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
enum AppleKeyboardConverter {
|
||||
static var isDynamicBasicKeyboardLayoutEnabled: Bool {
|
||||
IMKHelper.arrDynamicBasicKeyLayouts.contains(mgrPrefs.basicKeyboardLayout)
|
||||
import Foundation
|
||||
import InputMethodKit
|
||||
|
||||
// MARK: - IMKHelper by The vChewing Project (MIT License).
|
||||
|
||||
public enum IMKHelper {
|
||||
/// 威注音有專門統計過,實際上會有差異的英數鍵盤佈局只有這幾種。
|
||||
/// 精簡成這種清單的話,不但節省 SwiftUI 的繪製壓力,也方便使用者做選擇。
|
||||
public static let arrWhitelistedKeyLayoutsASCII: [String] = [
|
||||
"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",
|
||||
]
|
||||
|
||||
public static let arrDynamicBasicKeyLayouts: [String] = [
|
||||
"com.apple.keylayout.ZhuyinBopomofo",
|
||||
"com.apple.keylayout.ZhuyinEten",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingdachen",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingmitac",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingibm",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingseigyou",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingeten",
|
||||
"org.unknown.keylayout.vChewingDachen",
|
||||
"org.unknown.keylayout.vChewingFakeSeigyou",
|
||||
"org.unknown.keylayout.vChewingETen",
|
||||
"org.unknown.keylayout.vChewingIBM",
|
||||
"org.unknown.keylayout.vChewingMiTAC",
|
||||
]
|
||||
|
||||
public static var currentBasicKeyboardLayout: String {
|
||||
UserDefaults.standard.string(forKey: "BasicKeyboardLayout") ?? ""
|
||||
}
|
||||
|
||||
static func cnvStringApple2ABC(_ strProcessed: String) -> String {
|
||||
var strProcessed = strProcessed
|
||||
if isDynamicBasicKeyboardLayoutEnabled {
|
||||
public static var isDynamicBasicKeyboardLayoutEnabled: Bool {
|
||||
Self.arrDynamicBasicKeyLayouts.contains(currentBasicKeyboardLayout) || !currentBasicKeyboardLayout.isEmpty
|
||||
}
|
||||
|
||||
public static var allowedAlphanumericalTISInputSources: [TISInputSource] {
|
||||
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
|
||||
}
|
||||
|
||||
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
|
||||
// 為了保證清單順序,先弄兩個容器。
|
||||
var containerA: [TISInputSource?] = []
|
||||
var containerB: [TISInputSource?] = []
|
||||
var containerC: [TISInputSource?] = []
|
||||
|
||||
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
|
||||
|
||||
Self.arrWhitelistedKeyLayoutsASCII.forEach {
|
||||
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
|
||||
containerC.append(neta)
|
||||
}
|
||||
}
|
||||
|
||||
Self.arrDynamicBasicKeyLayouts.forEach {
|
||||
if let neta = rawDictionary[$0] {
|
||||
if neta.identifier.contains("com.apple") {
|
||||
containerA.append(neta)
|
||||
} else {
|
||||
containerB.append(neta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 這裡的 nil 是用來讓選單插入分隔符用的。
|
||||
if !containerA.isEmpty { containerA.append(nil) }
|
||||
if !containerB.isEmpty { containerB.append(nil) }
|
||||
|
||||
return containerA + containerB + containerC
|
||||
}
|
||||
|
||||
public struct CarbonKeyboardLayout {
|
||||
var strName: String = ""
|
||||
var strValue: String = ""
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 與輸入法的具體的安裝過程有關的命令
|
||||
|
||||
extension IMKHelper {
|
||||
@discardableResult public static func registerInputMethod() -> Int32 {
|
||||
TISInputSource.registerInputMethod() ? 0 : -1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Apple Keyboard Converter
|
||||
|
||||
extension NSEvent {
|
||||
public func convertedFromPhonabets(ignoringModifiers: Bool = false) -> String {
|
||||
if type == .flagsChanged { return "" }
|
||||
var strProcessed = charactersIgnoringModifiers ?? characters ?? ""
|
||||
if !ignoringModifiers {
|
||||
strProcessed = characters ?? ""
|
||||
}
|
||||
if IMKHelper.isDynamicBasicKeyboardLayoutEnabled {
|
||||
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
||||
switch mgrPrefs.basicKeyboardLayout {
|
||||
switch IMKHelper.currentBasicKeyboardLayout {
|
||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||
if strProcessed.count == 1, Character(strProcessed).isLowercase, Character(strProcessed).isASCII {
|
||||
strProcessed = strProcessed.uppercased()
|
||||
|
@ -125,7 +219,7 @@ enum AppleKeyboardConverter {
|
|||
// 摁了 Alt 的符號。
|
||||
if strProcessed == "—" { strProcessed = "-" }
|
||||
// Apple 倚天注音佈局追加符號糾正項目。
|
||||
if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||
if IMKHelper.currentBasicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||
switch strProcessed {
|
||||
case "_": strProcessed = "_"
|
||||
case ":": strProcessed = ":"
|
||||
|
@ -135,6 +229,10 @@ enum AppleKeyboardConverter {
|
|||
default: break
|
||||
}
|
||||
}
|
||||
// 糾正 macOS 內建的的動態注音鍵盤佈局的一個 bug。
|
||||
if "-·".contains(strProcessed), keyCode == 50 {
|
||||
strProcessed = "`"
|
||||
}
|
||||
}
|
||||
return strProcessed
|
||||
}
|
|
@ -9,87 +9,6 @@
|
|||
import Foundation
|
||||
import InputMethodKit
|
||||
|
||||
// MARK: - IMKHelper by The vChewing Project (MIT License).
|
||||
|
||||
enum IMKHelper {
|
||||
/// 威注音有專門統計過,實際上會有差異的英數鍵盤佈局只有這幾種。
|
||||
/// 精簡成這種清單的話,不但節省 SwiftUI 的繪製壓力,也方便使用者做選擇。
|
||||
static let arrWhitelistedKeyLayoutsASCII: [String] = [
|
||||
"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",
|
||||
]
|
||||
|
||||
static let arrDynamicBasicKeyLayouts: [String] = [
|
||||
"com.apple.keylayout.ZhuyinBopomofo",
|
||||
"com.apple.keylayout.ZhuyinEten",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingdachen",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingmitac",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingibm",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingseigyou",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingeten",
|
||||
"org.unknown.keylayout.vChewingDachen",
|
||||
"org.unknown.keylayout.vChewingFakeSeigyou",
|
||||
"org.unknown.keylayout.vChewingETen",
|
||||
"org.unknown.keylayout.vChewingIBM",
|
||||
"org.unknown.keylayout.vChewingMiTAC",
|
||||
]
|
||||
|
||||
static var allowedAlphanumericalTISInputSources: [TISInputSource] {
|
||||
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
|
||||
}
|
||||
|
||||
static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
|
||||
// 為了保證清單順序,先弄兩個容器。
|
||||
var containerA: [TISInputSource?] = []
|
||||
var containerB: [TISInputSource?] = []
|
||||
var containerC: [TISInputSource?] = []
|
||||
|
||||
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
|
||||
|
||||
IMKHelper.arrWhitelistedKeyLayoutsASCII.forEach {
|
||||
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
|
||||
containerC.append(neta)
|
||||
}
|
||||
}
|
||||
|
||||
IMKHelper.arrDynamicBasicKeyLayouts.forEach {
|
||||
if let neta = rawDictionary[$0] {
|
||||
if neta.identifier.contains("com.apple") {
|
||||
containerA.append(neta)
|
||||
} else {
|
||||
containerB.append(neta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 這裡的 nil 是用來讓選單插入分隔符用的。
|
||||
if !containerA.isEmpty { containerA.append(nil) }
|
||||
if !containerB.isEmpty { containerB.append(nil) }
|
||||
|
||||
return containerA + containerB + containerC
|
||||
}
|
||||
|
||||
struct CarbonKeyboardLayout {
|
||||
var strName: String = ""
|
||||
var strValue: String = ""
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 與輸入法的具體的安裝過程有關的命令
|
||||
|
||||
extension IMKHelper {
|
||||
@discardableResult static func registerInputMethod() -> Int32 {
|
||||
TISInputSource.registerInputMethod() ? 0 : -1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TISInputSource Extension by The vChewing Project (MIT License).
|
||||
|
||||
extension TISInputSource {
|
||||
|
@ -162,7 +81,7 @@ extension TISInputSource {
|
|||
}
|
||||
|
||||
public static func generate(from identifier: String) -> TISInputSource? {
|
||||
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier] ?? nil
|
||||
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
|
||||
}
|
||||
|
||||
public var inputModeID: String {
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,32 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "LangModelAssembly",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "LangModelAssembly",
|
||||
targets: ["LangModelAssembly"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../RMJay_LineReader"),
|
||||
.package(path: "../vChewing_Megrez"),
|
||||
.package(path: "../vChewing_PinyinPhonaConverter"),
|
||||
.package(path: "../vChewing_Shared"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "LangModelAssembly",
|
||||
dependencies: [
|
||||
.product(name: "LineReader", package: "RMJay_LineReader"),
|
||||
.product(name: "Megrez", package: "vChewing_Megrez"),
|
||||
.product(name: "Shared", package: "vChewing_Shared"),
|
||||
.product(name: "PinyinPhonaConverter", package: "vChewing_PinyinPhonaConverter"),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# LangModelAssembly
|
||||
|
||||
威注音輸入法的語言模組總成套裝。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -1,5 +1,3 @@
|
|||
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
|
@ -10,53 +8,46 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class SymbolNode {
|
||||
var title: String
|
||||
var children: [SymbolNode]?
|
||||
var previous: SymbolNode?
|
||||
public class CandidateNode {
|
||||
public var name: String
|
||||
public var members: [CandidateNode]
|
||||
public var previous: CandidateNode?
|
||||
|
||||
init(_ title: String, _ children: [SymbolNode]? = nil, previous: SymbolNode? = nil) {
|
||||
self.title = title
|
||||
self.children = children
|
||||
self.children?.forEach {
|
||||
$0.previous = self
|
||||
}
|
||||
public init(name: String, members: [CandidateNode] = [], previous: CandidateNode? = nil) {
|
||||
self.name = name
|
||||
self.members = members
|
||||
members.forEach { $0.previous = self }
|
||||
self.previous = previous
|
||||
}
|
||||
|
||||
init(_ title: String, symbols: String) {
|
||||
self.title = title
|
||||
children = Array(symbols).map { SymbolNode(String($0), nil) }
|
||||
children?.forEach {
|
||||
$0.previous = self
|
||||
}
|
||||
public init(name: String, symbols: String) {
|
||||
self.name = name
|
||||
members = Array(symbols).map { CandidateNode(name: String($0), symbols: []) }
|
||||
members.forEach { $0.previous = self }
|
||||
}
|
||||
|
||||
init(_ title: String, symbols: [String]) {
|
||||
self.title = title
|
||||
children = symbols.map { SymbolNode($0, nil) }
|
||||
children?.forEach {
|
||||
$0.previous = self
|
||||
}
|
||||
public init(name: String, symbols: [String]) {
|
||||
self.name = name
|
||||
members = symbols.map { CandidateNode(name: $0, symbols: []) }
|
||||
members.forEach { $0.previous = self }
|
||||
}
|
||||
|
||||
static func parseUserSymbolNodeData() {
|
||||
let url = mgrLangModel.userSymbolNodeDataURL()
|
||||
public static func load(url: URL) {
|
||||
// 這兩個變數單獨拿出來,省得每次都重建還要浪費算力。
|
||||
var arrLines = [String.SubSequence]()
|
||||
var fieldSlice = [Substring.SubSequence]()
|
||||
var arrChildren = [SymbolNode]()
|
||||
var arrMembers = [CandidateNode]()
|
||||
do {
|
||||
arrLines = try String(contentsOfFile: url.path, encoding: .utf8).split(separator: "\n")
|
||||
for strLine in arrLines.lazy.filter({ !$0.isEmpty }) {
|
||||
fieldSlice = strLine.split(separator: "=")
|
||||
switch fieldSlice.count {
|
||||
case 1: arrChildren.append(.init(String(fieldSlice[0])))
|
||||
case 2: arrChildren.append(.init(String(fieldSlice[0]), symbols: .init(fieldSlice[1])))
|
||||
case 1: arrMembers.append(.init(name: String(fieldSlice[0])))
|
||||
case 2: arrMembers.append(.init(name: String(fieldSlice[0]), symbols: .init(fieldSlice[1])))
|
||||
default: break
|
||||
}
|
||||
}
|
||||
root = arrChildren.isEmpty ? defaultSymbolRoot : .init("/", arrChildren)
|
||||
root = arrMembers.isEmpty ? defaultSymbolRoot : .init(name: "/", members: arrMembers)
|
||||
} catch {
|
||||
root = defaultSymbolRoot
|
||||
}
|
||||
|
@ -89,81 +80,81 @@ public class SymbolNode {
|
|||
static let catThai = NSLocalizedString("catThai", comment: "")
|
||||
static let catYi = NSLocalizedString("catYi", comment: "")
|
||||
|
||||
private(set) static var root: SymbolNode = .init("/")
|
||||
public private(set) static var root: CandidateNode = .init(name: "/")
|
||||
|
||||
private static let defaultSymbolRoot: SymbolNode = .init(
|
||||
"/",
|
||||
[
|
||||
SymbolNode(" "),
|
||||
SymbolNode("`"),
|
||||
SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%﹪@&#*・…—〜/\_―‖﹫﹟﹠﹡"),
|
||||
SymbolNode(
|
||||
catHoriBrackets,
|
||||
private static let defaultSymbolRoot: CandidateNode = .init(
|
||||
name: "/",
|
||||
members: [
|
||||
CandidateNode(name: " "),
|
||||
CandidateNode(name: "`"),
|
||||
CandidateNode(name: catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%﹪@&#*・…—〜/\_―‖﹫﹟﹠﹡"),
|
||||
CandidateNode(
|
||||
name: catHoriBrackets,
|
||||
symbols:
|
||||
"()[]{}〈〉《》「」『』【】〔〕〖〗〘〙〚〛︗︘︷︸︹︺︻︼︽︾︿﹀﹁﹂﹃﹄﹇﹈︵︶[]{}⁅⁆⎡⎢⎣⎤⎥⎦⎧⎨⎩⎪⎫⎬⎭⎰⎱「」❬❭❰❱❲❳❴❵⟦⟧⟨⟩⟪⟫⟬⟭⦃⦄⦇⦈⦉⦊⦋⦌⦍⦎⦏⦐⦑⦒⦓⦔⦕⦖⦗⦘⧼⧽⸂⸃⸄⸅⸉⸊⸌⸍⸜⸝⸢⸣⸤⸥⸦⸧⎴⎵⎶⏞⏟⏠⏡﹙﹚﹛﹜﹝﹞﹤﹥‘’“”〝〞‵′″'"
|
||||
),
|
||||
SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
|
||||
SymbolNode(
|
||||
catAlphabets,
|
||||
CandidateNode(name: catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
|
||||
CandidateNode(
|
||||
name: catAlphabets,
|
||||
symbols:
|
||||
"αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩабвгдежзийклмнопрстуфхцчшщъыьэюяёАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯЁàáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜɑɡ¨·"
|
||||
),
|
||||
SymbolNode(
|
||||
catSpecialNumbers, symbols: "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ〇〡〢〣〤〥〦〧〨〩"
|
||||
CandidateNode(
|
||||
name: catSpecialNumbers, symbols: "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ〇〡〢〣〤〥〦〧〨〩"
|
||||
),
|
||||
SymbolNode(
|
||||
catMathSymbols,
|
||||
CandidateNode(
|
||||
name: catMathSymbols,
|
||||
symbols:
|
||||
"﹢﹤﹥+-<=>✕%+<=>¡¢«°±µ»¼½¾¿×÷ˇθπ‰₠₡₢₣₤₥₦₧₨₩₪₫€℀℁℃℅℆℉⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝∞∟∠∡∢∣∤∥∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊙⊚⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊰⊱⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⊿⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱"
|
||||
),
|
||||
SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷¤¢¥£$﹩₩"),
|
||||
SymbolNode(
|
||||
catSpecialSymbols,
|
||||
CandidateNode(name: catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷¤¢¥£$﹩₩"),
|
||||
CandidateNode(
|
||||
name: catSpecialSymbols,
|
||||
symbols:
|
||||
"↩⌘⎋⏏⇥⇤⇪⇧⌤⌥⎇␣⌃⌄⌅⌆⌦⌫⌧⇱↖↸⇲↘⇞⇟↑↓←→⇡⇣⇠⇢⚙⇭⌽⌀⌁⌂⌐⌑⌒⌓⌔⌕⌖⌗⌙⌨⎄⎅⎆⎈⎉⎊⎌⌚⌛⎗⎘⎙⎚⎀⎁⎂⎃⌇⌈⌉⌊⌋⌌⌍⌏⌠⎮⌡⌢⌣⌜⌝⌞⌟⁒〈〉⌬⌭⌮⌯⌰⌱⌲⌳↗↙↺⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼№℡§〒♀♂↯¶©®™🜀🜁🜂🜃🜄🜅🜆🜇🜈🜉🜊🜋🜌🜍🜎🜏🜐🜑🜒🜓🜔🜕🜖🜗🜘🜙🜚🜛🜜🜝🜞🜟🜠🜡🜢🜣🜤🜥🜦🜧🜨🜩🜪🜫🜬🜭🜮🜯🜰🜱🜲🜳🜴🜵🜶🜷🜸🜹🜺🜻🜼🜽🜾🜿🝀🝁🝂🝃🝄🝅🝆🝇🝈🝉🝊🝋🝌🝍🝎🝏🝐🝑🝒🝓🝔🝕🝖🝗🝘🝙🝚🝛🝜🝝🝞🝟🝠🝡🝢🝣🝤🝥🝦🝧🝨🝩🝪🝫🝬🝭🝮🝯🝰🝱🝲🝳↚↛↜↝↞↟↠↡↢↣↤↥↦↧↨↪↫↬↭↮"
|
||||
),
|
||||
SymbolNode(
|
||||
catUnicodeSymbols,
|
||||
CandidateNode(
|
||||
name: catUnicodeSymbols,
|
||||
symbols:
|
||||
"※∮∴∵∽☀☁☂☃☺☻♠♣♥♦♨♩♪♫♬♭♯■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▲△▴▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◆◇◈◉◊○◌◍◎●◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴◵◶◷◸◹◺◻◼◽◾◿☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♡♢♤♧♮♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌⚍⚎⚏⚐⚑⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜⚝⚞⚟⚠⚡⚢⚣⚤⚥⚦⚧⚨⚩⚪⚫⚬⚭⚮⚯⚰⚱⚲⚳⚴⚵⚶⚷⚸⚹⚺⚻⚼⚽⚾⚿⛀⛁⛂⛃⛄⛅⛆⛇⛈⛉⛊⛋⛌⛍⛎⛏⛐⛑⛒⛓⛔⛕⛖⛗⛘⛙⛚⛛⛜⛝⛞⛟⛠⛡⛢⛣⛤⛥⛦⛧⛨⛩⛪⛫⛬⛭⛮⛯⛰⛱⛲⛳⛴⛵⛶⛷⛸⛹⛺⛻⛼⛽⛾⛿✁✂✃✄✅✆✇✈✉✊✋✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✨✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❌❍❎❏❐❑❒❓❔❕❖❗❘❙❚❛❜❝❞❟❠❡❢❣❤❥❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵➔➕➖➗➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➰➱➲➳➴➵➶➷➸➹➺➻➼➽➾➿⬀⬁⬂⬃⬄⬅⬆⬇⬈⬉⬊⬋⬌⬍⬎⬏⬐⬑⬒⬓⬔⬕⬖⬗⬘⬙⬚⬛⬜⬝⬞⬟⬠⬡⬢⬣⬤⬥⬦⬧⬨⬩⬪⬫⬬⬭⬮⬯⬰⬱⬲⬳⬴⬵⬶⬷⬸⬹⬺⬻⬼⬽⬾⬿⭀⭁⭂⭃⭄⭅⭆⭇⭈⭉⭊⭋⭌⭐⭑⭒⭓⭔⭕⭖⭗⭘⭙₮〠〶ↀↁↂ₭〇〄㉿〆₯ℂ℄"
|
||||
),
|
||||
SymbolNode(
|
||||
catMusicSymbols,
|
||||
CandidateNode(
|
||||
name: catMusicSymbols,
|
||||
symbols:
|
||||
"𝄀𝄁𝄂𝄃𝄄𝄅𝄆𝄇𝄈𝄉𝄊𝄋𝄌𝄍𝄎𝄏𝄐𝄑𝄒𝄓𝄔𝄕𝄖𝄗𝄘𝄙𝄚𝄛𝄜𝄝𝄞𝄟𝄠𝄡𝄢𝄣𝄤𝄥𝄦𝄩𝄪𝄫𝄬𝄭𝄮𝄯𝄰𝄱𝄲𝄳𝄴𝄵𝄶𝄷𝄸𝄹𝄺𝄻𝄼𝄽𝄾𝄿𝅀𝅁𝅂𝅃𝅄𝅅𝅆𝅇𝅈𝅉𝅊𝅋𝅌𝅍𝅎𝅏𝅐𝅑𝅒𝅓𝅔𝅕𝅖𝅗𝅗𝅥𝅘𝅘𝅥𝅘𝅥𝅮𝅘𝅥𝅯𝅘𝅥𝅰𝅘𝅥𝅱𝅘𝅧𝅨𝅩𝅥𝅲𝅥𝅦𝅙𝅚𝅛𝅜𝅝𝅪𝅫𝅬𝅮𝅯𝅰𝅱𝅲𝅻𝅼𝅽𝅾𝅿𝆀𝆁𝆂𝅭𝆃𝆄𝆊𝆋𝆅𝆆𝆇𝆈𝆉𝆌𝆍𝆎𝆏𝆐𝆑𝆒𝆓𝆔𝆕𝆖𝆗𝆘𝆙𝆚𝆛𝆜𝆝𝆞𝆟𝆠𝆡𝆢𝆣𝆤𝆥𝆦𝆧𝆨𝆩𝆪𝆫𝆬𝆭𝆮𝆯𝆰𝆱𝆲𝆳𝆴𝆵𝆶𝆷𝆸𝆹𝆹𝅥𝆹𝅥𝅮𝆹𝅥𝅯𝆺𝆺𝅥𝆺𝅥𝅮𝆺𝅥𝅯𝇁𝇂𝇃𝇄𝇅𝇆𝇇𝇈𝇉𝇊𝇋𝇌𝇍𝇎𝇏𝇐𝇑𝇒𝇓𝇔𝇕𝇖𝇗𝇘𝇙𝇚𝇛𝇜𝇝𝇞𝇟𝇠𝇡𝇢𝇣𝇤𝇥𝇦𝇧𝇨"
|
||||
),
|
||||
SymbolNode(catCircledKanjis, symbols: "㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊚㊛㊜㊝㊞㊟㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉🈚︎🈯︎"),
|
||||
SymbolNode(
|
||||
catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾"
|
||||
CandidateNode(name: catCircledKanjis, symbols: "㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊚㊛㊜㊝㊞㊟㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉🈚︎🈯︎"),
|
||||
CandidateNode(
|
||||
name: catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾"
|
||||
),
|
||||
SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
|
||||
SymbolNode(catSingleTableLines, symbols: "─│┌┐└┕┘├┤┬┴┼═╞╡╪╭╮╯╰▕"),
|
||||
SymbolNode(catDoubleTableLines, symbols: "═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬"),
|
||||
SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
|
||||
SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\ˍ━┃▕〓╳∏ˆ⌒┄┅┆┇┈┉┊┋"),
|
||||
SymbolNode(
|
||||
catKana,
|
||||
CandidateNode(name: catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
|
||||
CandidateNode(name: catSingleTableLines, symbols: "─│┌┐└┕┘├┤┬┴┼═╞╡╪╭╮╯╰▕"),
|
||||
CandidateNode(name: catDoubleTableLines, symbols: "═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬"),
|
||||
CandidateNode(name: catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
|
||||
CandidateNode(name: catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\ˍ━┃▕〓╳∏ˆ⌒┄┅┆┇┈┉┊┋"),
|
||||
CandidateNode(
|
||||
name: catKana,
|
||||
symbols:
|
||||
"ぁあぃいぅうゔぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばひびふぶへべほぼまみむめもゃやゅゆょよらら゚りり゚るる゚れれ゚ろろ゚ゎわわ゙ゐゐ゙ゑゑ゙をを゙んゕゖゝゞゟ〻゠ァアィイゥウヴェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨララ゚リリ゚ルル゚レレ゚ロロ゚ヮワヷヰヸヱヹヲヺンヵヶ・ーヽヾヿ々ㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン ゙ ゚〲〱〳〴〵"
|
||||
),
|
||||
SymbolNode(
|
||||
catCombinations,
|
||||
CandidateNode(
|
||||
name: catCombinations,
|
||||
symbols:
|
||||
"㍘㍙㍚㍛㍜㍝㍞㍟㍠㍡㍢㍣㍤㍥㍦㍧㍨㍩㍪㍫㍬㍭㍮㍯㍰㏠㏡㏢㏣㏤㏥㏦㏧㏨㏩㏪㏫㏬㏭㏮㏯㏰㏱㏲㏳㏴㏵㏶㏷㏸㏹㏺㏻㏼㏽㏾㍱㍲㍳㍴㍵㍶㍷㍸㍹㍺㎀㎁㎂㎃㎄㎅㎆㎇㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏋㏌㏍㏎㏏㏐㏑㏒㏓㏔㏕㏖㏗㏘㏙㏚㏛㏜㏝㏞㏟㏿㋿㍼㍽㍾㍻㍿㌀㌁㌂㌃㌄㌅㌆㌇㌈㌉㌊㌋㌌㌍㌎㌏㌐㌑㌒㌓㌔㌕㌖㌗㌘㌙㌚㌛㌜㌝㌞㌟㌠㌡㌢㌣㌤㌥㌦㌧㌨㌩㌪㌫㌬㌭㌮㌯㌰㌱㌲㌳㌴㌵㌶㌷㌸㌹㌺㌻㌼㌽㌾㌿㍀㍁㍂㍃㍄㍅㍆㍇㍈㍉㍊㍋㍌㍍㍎㍏㍐㍑㍒㍓㍔㍕㍖㍗"
|
||||
),
|
||||
SymbolNode(
|
||||
catPhonabets, symbols: "ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩㄪㄫㄬㄭㄮㄯㆵㆠㆡㆢㆣㆤㆥㆦㆧㆨㆩㆪㆫㆬㆭㆮㆯㆰㆱㆲㆳㆴㆶㆷㆸㆹㆺㆻㆼㆽㆾㆿ˙ˊˇˋ˪˫"
|
||||
CandidateNode(
|
||||
name: catPhonabets, symbols: "ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩㄪㄫㄬㄭㄮㄯㆵㆠㆡㆢㆣㆤㆥㆦㆧㆨㆩㆪㆫㆬㆭㆮㆯㆰㆱㆲㆳㆴㆶㆷㆸㆹㆺㆻㆼㆽㆾㆿ˙ˊˇˋ˪˫"
|
||||
),
|
||||
SymbolNode(
|
||||
catCircledASCII,
|
||||
CandidateNode(
|
||||
name: catCircledASCII,
|
||||
symbols:
|
||||
"①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓂ︎ⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿︎🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉"
|
||||
),
|
||||
SymbolNode(
|
||||
catBracketedASCII, symbols: "⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵🄐🄑🄒🄓🄔🄕🄖🄗🄘🄙🄚🄛🄜🄝🄞🄟🄠🄡🄢🄣🄤🄥🄦🄧🄨🄩"
|
||||
CandidateNode(
|
||||
name: catBracketedASCII, symbols: "⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵🄐🄑🄒🄓🄔🄕🄖🄗🄘🄙🄚🄛🄜🄝🄞🄟🄠🄡🄢🄣🄤🄥🄦🄧🄨🄩"
|
||||
),
|
||||
SymbolNode(
|
||||
catThai,
|
||||
CandidateNode(
|
||||
name: catThai,
|
||||
symbols: [
|
||||
"ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ",
|
||||
"ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ",
|
||||
|
@ -171,8 +162,8 @@ public class SymbolNode {
|
|||
"็", "่", "้", "๊", "๋", "์", "ํ", "๎", "๏", "๐", "๑", "๒", "๓", "๔", "๕", "๖", "๗", "๘", "๙", "๚", "๛",
|
||||
]
|
||||
),
|
||||
SymbolNode(
|
||||
catYi,
|
||||
CandidateNode(
|
||||
name: catYi,
|
||||
symbols:
|
||||
"ꀀꀁꀂꀃꀄꀅꀆꀇꀈꀉꀊꀋꀌꀍꀎꀏꀐꀑꀒꀓꀔꀕꀖꀗꀘꀙꀚꀛꀜꀝꀞꀟꀠꀡꀢꀣꀤꀥꀦꀧꀨꀩꀪꀫꀬꀭꀮꀯꀰꀱꀲꀳꀴꀵꀶꀷꀸꀹꀺꀻꀼꀽꀾꀿꁀꁁꁂꁃꁄꁅꁆꁇꁈꁉꁊꁋꁌꁍꁎꁏꁐꁑꁒꁓꁔꁕꁖꁗꁘꁙꁚꁛꁜꁝꁞꁟꁠꁡꁢꁣꁤꁥꁦꁧꁨꁩꁪꁫꁬꁭꁮꁯꁰꁱꁲꁳꁴꁵꁶꁷꁸꁹꁺꁻꁼꁽꁾꁿꂀꂁꂂꂃꂄꂅꂆꂇꂈꂉꂊꂋꂌꂍꂎꂏꂐꂑꂒꂓꂔꂕꂖꂗꂘꂙꂚꂛꂜꂝꂞꂟꂠꂡꂢꂣꂤꂥꂦꂧꂨꂩꂪꂫꂬꂭꂮꂯꂰꂱꂲꂳꂴꂵꂶꂷꂸꂹꂺꂻꂼꂽꂾꂿꃀꃁꃂꃃꃄꃅꃆꃇꃈꃉꃊꃋꃌꃍꃎꃏꃐꃑꃒꃓꃔꃕꃖꃗꃘꃙꃚꃛꃜꃝꃞꃟꃠꃡꃢꃣꃤꃥꃦꃧꃨꃩꃪꃫꃬꃭꃮꃯꃰꃱꃲꃳꃴꃵꃶꃷꃸꃹꃺꃻꃼꃽꃾꃿꄀꄁꄂꄃꄄꄅꄆꄇꄈꄉꄊꄋꄌꄍꄎꄏꄐꄑꄒꄓꄔꄕꄖꄗꄘꄙꄚꄛꄜꄝꄞꄟꄠꄡꄢꄣꄤꄥꄦꄧꄨꄩꄪꄫꄬꄭꄮꄯꄰꄱꄲꄳꄴꄵꄶꄷꄸꄹꄺꄻꄼꄽꄾꄿꅀꅁꅂꅃꅄꅅꅆꅇꅈꅉꅊꅋꅌꅍꅎꅏꅐꅑꅒꅓꅔꅕꅖꅗꅘꅙꅚꅛꅜꅝꅞꅟꅠꅡꅢꅣꅤꅥꅦꅧꅨꅩꅪꅫꅬꅭꅮꅯꅰꅱꅲꅳꅴꅵꅶꅷꅸꅹꅺꅻꅼꅽꅾꅿꆀꆁꆂꆃꆄꆅꆆꆇꆈꆉꆊꆋꆌꆍꆎꆏꆐꆑꆒꆓꆔꆕꆖꆗꆘꆙꆚꆛꆜꆝꆞꆟꆠꆡꆢꆣꆤꆥꆦꆧꆨꆩꆪꆫꆬꆭꆮꆯꆰꆱꆲꆳꆴꆵꆶꆷꆸꆹꆺꆻꆼꆽꆾꆿꇀꇁꇂꇃꇄꇅꇆꇇꇈꇉꇊꇋꇌꇍꇎꇏꇐꇑꇒꇓꇔꇕꇖꇗꇘꇙꇚꇛꇜꇝꇞꇟꇠꇡꇢꇣꇤꇥꇦꇧꇨꇩꇪꇫꇬꇭꇮꇯꇰꇱꇲꇳꇴꇵꇶꇷꇸꇹꇺꇻꇼꇽꇾꇿꈀꈁꈂꈃꈄꈅꈆꈇꈈꈉꈊꈋꈌꈍꈎꈏꈐꈑꈒꈓꈔꈕꈖꈗꈘꈙꈚꈛꈜꈝꈞꈟꈠꈡꈢꈣꈤꈥꈦꈧꈨꈩꈪꈫꈬꈭꈮꈯꈰꈱꈲꈳꈴꈵꈶꈷꈸꈹꈺꈻꈼꈽꈾꈿꉀꉁꉂꉃꉄꉅꉆꉇꉈꉉꉊꉋꉌꉍꉎꉏꉐꉑꉒꉓꉔꉕꉖꉗꉘꉙꉚꉛꉜꉝꉞꉟꉠꉡꉢꉣꉤꉥꉦꉧꉨꉩꉪꉫꉬꉭꉮꉯꉰꉱꉲꉳꉴꉵꉶꉷꉸꉹꉺꉻꉼꉽꉾꉿꊀꊁꊂꊃꊄꊅꊆꊇꊈꊉꊊꊋꊌꊍꊎꊏꊐꊑꊒꊓꊔꊕꊖꊗꊘꊙꊚꊛꊜꊝꊞꊟꊠꊡꊢꊣꊤꊥꊦꊧꊨꊩꊪꊫꊬꊭꊮꊯꊰꊱꊲꊳꊴꊵꊶꊷꊸꊹꊺꊻꊼꊽꊾꊿꋀꋁꋂꋃꋄꋅꋆꋇꋈꋉꋊꋋꋌꋍꋎꋏꋐꋑꋒꋓꋔꋕꋖꋗꋘꋙꋚꋛꋜꋝꋞꋟꋠꋡꋢꋣꋤꋥꋦꋧꋨꋩꋪꋫꋬꋭꋮꋯꋰꋱꋲꋳꋴꋵꋶꋷꋸꋹꋺꋻꋼꋽꋾꋿꌀꌁꌂꌃꌄꌅꌆꌇꌈꌉꌊꌋꌌꌍꌎꌏꌐꌑꌒꌓꌔꌕꌖꌗꌘꌙꌚꌛꌜꌝꌞꌟꌠꌡꌢꌣꌤꌥꌦꌧꌨꌩꌪꌫꌬꌭꌮꌯꌰꌱꌲꌳꌴꌵꌶꌷꌸꌹꌺꌻꌼꌽꌾꌿꍀꍁꍂꍃꍄꍅꍆꍇꍈꍉꍊꍋꍌꍍꍎꍏꍐꍑꍒꍓꍔꍕꍖꍗꍘꍙꍚꍛꍜꍝꍞꍟꍠꍡꍢꍣꍤꍥꍦꍧꍨꍩꍪꍫꍬꍭꍮꍯꍰꍱꍲꍳꍴꍵꍶꍷꍸꍹꍺꍻꍼꍽꍾꍿꎀꎁꎂꎃꎄꎅꎆꎇꎈꎉꎊꎋꎌꎍꎎꎏꎐꎑꎒꎓꎔꎕꎖꎗꎘꎙꎚꎛꎜꎝꎞꎟꎠꎡꎢꎣꎤꎥꎦꎧꎨꎩꎪꎫꎬꎭꎮꎯꎰꎱꎲꎳꎴꎵꎶꎷꎸꎹꎺꎻꎼꎽꎾꎿꏀꏁꏂꏃꏄꏅꏆꏇꏈꏉꏊꏋꏌꏍꏎꏏꏐꏑꏒꏓꏔꏕꏖꏗꏘꏙꏚꏛꏜꏝꏞꏟꏠꏡꏢꏣꏤꏥꏦꏧꏨꏩꏪꏫꏬꏭꏮꏯꏰꏱꏲꏳꏴꏵꏶꏷꏸꏹꏺꏻꏼꏽꏾꏿꐀꐁꐂꐃꐄꐅꐆꐇꐈꐉꐊꐋꐌꐍꐎꐏꐐꐑꐒꐓꐔꐕꐖꐗꐘꐙꐚꐛꐜꐝꐞꐟꐠꐡꐢꐣꐤꐥꐦꐧꐨꐩꐪꐫꐬꐭꐮꐯꐰꐱꐲꐳꐴꐵꐶꐷꐸꐹꐺꐻꐼꐽꐾꐿꑀꑁꑂꑃꑄꑅꑆꑇꑈꑉꑊꑋꑌꑍꑎꑏꑐꑑꑒꑓꑔꑕꑖꑗꑘꑙꑚꑛꑜꑝꑞꑟꑠꑡꑢꑣꑤꑥꑦꑧꑨꑩꑪꑫꑬꑭꑮꑯꑰꑱꑲꑳꑴꑵꑶꑷꑸꑹꑺꑻꑼꑽꑾꑿꒀꒁꒂꒃꒄꒅꒆꒇꒈꒉꒊꒋꒌ꒐꒑꒒꒓꒔꒕꒖꒗꒘꒙꒚꒛꒜꒝꒞꒟꒠꒡꒢꒣꒤꒥꒦꒧꒨꒩꒪꒫꒬꒭꒮꒯꒰꒱꒲꒳꒴꒵꒶꒷꒸꒹꒺꒻꒼꒽꒾꒿꓀꓁꓂꓃꓄꓅꓆"
|
||||
),
|
|
@ -7,8 +7,10 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import LineReader
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
extension vChewingLM {
|
||||
public enum LMConsolidator {
|
||||
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
|
||||
|
||||
|
@ -22,19 +24,19 @@ extension vChewing {
|
|||
let lineReader = try LineReader(file: fileHandle)
|
||||
for strLine in lineReader { // 不需要 i=0,因為第一遍迴圈就出結果。
|
||||
if strLine != kPragmaHeader {
|
||||
IME.prtDebugIntel("Header Mismatch, Starting In-Place Consolidation.")
|
||||
vCLog("Header Mismatch, Starting In-Place Consolidation.")
|
||||
return false
|
||||
} else {
|
||||
IME.prtDebugIntel("Header Verification Succeeded: \(strLine).")
|
||||
vCLog("Header Verification Succeeded: \(strLine).")
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
IME.prtDebugIntel("Header Verification Failed: File Access Error.")
|
||||
vCLog("Header Verification Failed: File Access Error.")
|
||||
return false
|
||||
}
|
||||
}
|
||||
IME.prtDebugIntel("Header Verification Failed: File Missing.")
|
||||
vCLog("Header Verification Failed: File Missing.")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -50,7 +52,7 @@ extension vChewing {
|
|||
/// 注意:Swift 版 LMConsolidator 並未在此安排對 EOF 的去重複工序。
|
||||
/// 但這個函式執行完之後往往就會 consolidate() 整理格式,所以不會有差。
|
||||
if !strIncoming.hasSuffix("\n") {
|
||||
IME.prtDebugIntel("EOF Fix Necessity Confirmed, Start Fixing.")
|
||||
vCLog("EOF Fix Necessity Confirmed, Start Fixing.")
|
||||
if let writeFile = FileHandle(forUpdatingAtPath: path),
|
||||
let endl = "\n".data(using: .utf8)
|
||||
{
|
||||
|
@ -62,14 +64,14 @@ extension vChewing {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
IME.prtDebugIntel("EOF Fix Failed w/ File: \(path)")
|
||||
IME.prtDebugIntel("EOF Fix Failed w/ Error: \(error).")
|
||||
vCLog("EOF Fix Failed w/ File: \(path)")
|
||||
vCLog("EOF Fix Failed w/ Error: \(error).")
|
||||
return false
|
||||
}
|
||||
IME.prtDebugIntel("EOF Successfully Ensured (with possible autofixes performed).")
|
||||
vCLog("EOF Successfully Ensured (with possible autofixes performed).")
|
||||
return true
|
||||
}
|
||||
IME.prtDebugIntel("EOF Fix Failed: File Missing at \(path).")
|
||||
vCLog("EOF Fix Failed: File Missing at \(path).")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -129,14 +131,14 @@ extension vChewing {
|
|||
try strProcessed.write(to: urlPath, atomically: false, encoding: .utf8)
|
||||
|
||||
} catch {
|
||||
IME.prtDebugIntel("Consolidation Failed w/ File: \(path)")
|
||||
IME.prtDebugIntel("Consolidation Failed w/ Error: \(error).")
|
||||
vCLog("Consolidation Failed w/ File: \(path)")
|
||||
vCLog("Consolidation Failed w/ Error: \(error).")
|
||||
return false
|
||||
}
|
||||
IME.prtDebugIntel("Either Consolidation Successful Or No-Need-To-Consolidate.")
|
||||
vCLog("Either Consolidation Successful Or No-Need-To-Consolidate.")
|
||||
return true
|
||||
}
|
||||
IME.prtDebugIntel("Consolidation Failed: File Missing at \(path).")
|
||||
vCLog("Consolidation Failed: File Missing at \(path).")
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// Refactored from the ObjCpp-version of this class by:
|
||||
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
// ... with NTL restriction stating that:
|
||||
|
@ -9,8 +7,10 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
extension vChewingLM {
|
||||
/// 語言模組副本化模組(LMInstantiator,下稱「LMI」)自身為符合天權星組字引擎內
|
||||
/// 的 LangModelProtocol 協定的模組、統籌且整理來自其它子模組的資料(包括使
|
||||
/// 用者語彙、繪文字模組、語彙濾除表、原廠語言模組等)。
|
||||
|
@ -33,6 +33,14 @@ extension vChewing {
|
|||
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) {
|
||||
self.isCHS = isCHS
|
||||
}
|
||||
|
||||
/// 介紹一下幾個通用的語言模組型別:
|
||||
/// ----------------------
|
||||
|
@ -56,10 +64,10 @@ extension vChewing {
|
|||
|
||||
// 簡體中文模式與繁體中文模式共用全字庫擴展模組,故靜態處理。
|
||||
// 不然,每個模式都會讀入一份全字庫,會多佔用 100MB 記憶體。
|
||||
static var lmCNS = vChewing.LMCoreNS(
|
||||
static var lmCNS = vChewingLM.LMCoreNS(
|
||||
reverse: true, consolidate: false, defaultScore: -11.0, forceDefaultScore: false
|
||||
)
|
||||
static var lmSymbols = vChewing.LMCoreNS(
|
||||
static var lmSymbols = vChewingLM.LMCoreNS(
|
||||
reverse: true, consolidate: false, defaultScore: -13.0, forceDefaultScore: false
|
||||
)
|
||||
|
||||
|
@ -74,7 +82,7 @@ extension vChewing {
|
|||
var lmUserSymbols = LMCoreEX(
|
||||
reverse: true, consolidate: true, defaultScore: -12.0, forceDefaultScore: true
|
||||
)
|
||||
var lmReplacements = LMReplacments()
|
||||
var lmReplacements = LMReplacements()
|
||||
var lmAssociates = LMAssociates()
|
||||
var lmPlainBopomofo = LMPlainBopomofo()
|
||||
|
||||
|
@ -84,19 +92,19 @@ extension vChewing {
|
|||
public func loadLanguageModel(path: String) {
|
||||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
lmCore.open(path)
|
||||
IME.prtDebugIntel("lmCore: \(lmCore.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmCore: \(lmCore.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmCore: File access failure: \(path)")
|
||||
vCLog("lmCore: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
public var isCNSDataLoaded: Bool { vChewing.LMInstantiator.lmCNS.isLoaded() }
|
||||
public var isCNSDataLoaded: Bool { vChewingLM.LMInstantiator.lmCNS.isLoaded() }
|
||||
public func loadCNSData(path: String) {
|
||||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
vChewing.LMInstantiator.lmCNS.open(path)
|
||||
IME.prtDebugIntel("lmCNS: \(vChewing.LMInstantiator.lmCNS.count) entries of data loaded from: \(path)")
|
||||
vChewingLM.LMInstantiator.lmCNS.open(path)
|
||||
vCLog("lmCNS: \(vChewingLM.LMInstantiator.lmCNS.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmCNS: File access failure: \(path)")
|
||||
vCLog("lmCNS: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,19 +112,20 @@ extension vChewing {
|
|||
public func loadMiscData(path: String) {
|
||||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
lmMisc.open(path)
|
||||
IME.prtDebugIntel("lmMisc: \(lmMisc.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmMisc: \(lmMisc.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmMisc: File access failure: \(path)")
|
||||
vCLog("lmMisc: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
public var isSymbolDataLoaded: Bool { vChewing.LMInstantiator.lmSymbols.isLoaded() }
|
||||
public var isSymbolDataLoaded: Bool { vChewingLM.LMInstantiator.lmSymbols.isLoaded() }
|
||||
public func loadSymbolData(path: String) {
|
||||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
vChewing.LMInstantiator.lmSymbols.open(path)
|
||||
IME.prtDebugIntel("lmSymbol: \(vChewing.LMInstantiator.lmSymbols.count) entries of data loaded from: \(path)")
|
||||
vChewingLM.LMInstantiator.lmSymbols.open(path)
|
||||
vCLog(
|
||||
"lmSymbol: \(vChewingLM.LMInstantiator.lmSymbols.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmSymbols: File access failure: \(path)")
|
||||
vCLog("lmSymbols: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,16 +133,16 @@ extension vChewing {
|
|||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
lmUserPhrases.close()
|
||||
lmUserPhrases.open(path)
|
||||
IME.prtDebugIntel("lmUserPhrases: \(lmUserPhrases.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmUserPhrases: \(lmUserPhrases.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmUserPhrases: File access failure: \(path)")
|
||||
vCLog("lmUserPhrases: File access failure: \(path)")
|
||||
}
|
||||
if FileManager.default.isReadableFile(atPath: filterPath) {
|
||||
lmFiltered.close()
|
||||
lmFiltered.open(filterPath)
|
||||
IME.prtDebugIntel("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmFiltered: File access failure: \(path)")
|
||||
vCLog("lmFiltered: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,9 +150,9 @@ extension vChewing {
|
|||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
lmUserSymbols.close()
|
||||
lmUserSymbols.open(path)
|
||||
IME.prtDebugIntel("lmUserSymbol: \(lmUserSymbols.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmUserSymbol: \(lmUserSymbols.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmUserSymbol: File access failure: \(path)")
|
||||
vCLog("lmUserSymbol: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,9 +160,9 @@ extension vChewing {
|
|||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
lmAssociates.close()
|
||||
lmAssociates.open(path)
|
||||
IME.prtDebugIntel("lmAssociates: \(lmAssociates.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmAssociates: \(lmAssociates.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmAssociates: File access failure: \(path)")
|
||||
vCLog("lmAssociates: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,9 +170,9 @@ extension vChewing {
|
|||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
lmReplacements.close()
|
||||
lmReplacements.open(path)
|
||||
IME.prtDebugIntel("lmReplacements: \(lmReplacements.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmReplacements: \(lmReplacements.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmReplacements: File access failure: \(path)")
|
||||
vCLog("lmReplacements: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,16 +180,32 @@ extension vChewing {
|
|||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
lmPlainBopomofo.close()
|
||||
lmPlainBopomofo.open(path)
|
||||
IME.prtDebugIntel("lmPlainBopomofo: \(lmPlainBopomofo.count) entries of data loaded from: \(path)")
|
||||
vCLog("lmPlainBopomofo: \(lmPlainBopomofo.count) entries of data loaded from: \(path)")
|
||||
} else {
|
||||
IME.prtDebugIntel("lmPlainBopomofo: File access failure: \(path)")
|
||||
vCLog("lmPlainBopomofo: File access failure: \(path)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 核心函式(對外)
|
||||
|
||||
/// 威注音輸入法目前尚未具備對雙元圖的處理能力,故停用該函式。
|
||||
// public func bigramsFor(preceedingKey: String, key: String) -> [Megrez.Bigram] { }
|
||||
public func hasAssociatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> Bool {
|
||||
lmAssociates.hasValuesFor(pair: pair)
|
||||
}
|
||||
|
||||
public func associatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] {
|
||||
lmAssociates.valuesFor(pair: pair)
|
||||
}
|
||||
|
||||
/// 根據給定的索引鍵來確認各個資料庫陣列內是否存在對應的資料。
|
||||
/// - Parameter key: 索引鍵。
|
||||
/// - Returns: 是否在庫。
|
||||
public func hasUnigramsFor(key: String) -> Bool {
|
||||
if key == " " { return true }
|
||||
if !lmFiltered.hasUnigramsFor(key: key) {
|
||||
return lmUserPhrases.hasUnigramsFor(key: key) || lmCore.hasUnigramsFor(key: key)
|
||||
}
|
||||
return !unigramsFor(key: key).isEmpty
|
||||
}
|
||||
|
||||
/// 給定讀音字串,讓 LMI 給出對應的經過處理的單元圖陣列。
|
||||
/// - Parameter key: 給定的讀音字串。
|
||||
|
@ -193,7 +218,7 @@ extension vChewing {
|
|||
var rawAllUnigrams: [Megrez.Unigram] = []
|
||||
|
||||
// 如果有檢測到使用者自訂逐字選字語料庫內的相關資料的話,在這裡先插入。
|
||||
if mgrPrefs.useSCPCTypingMode {
|
||||
if isSCPCEnabled {
|
||||
rawAllUnigrams += lmPlainBopomofo.valuesFor(key: key).map { Megrez.Unigram(value: $0, score: 0) }
|
||||
}
|
||||
|
||||
|
@ -207,77 +232,38 @@ extension vChewing {
|
|||
rawAllUnigrams += lmCore.unigramsFor(key: key)
|
||||
|
||||
if isCNSEnabled {
|
||||
rawAllUnigrams += vChewing.LMInstantiator.lmCNS.unigramsFor(key: key)
|
||||
rawAllUnigrams += vChewingLM.LMInstantiator.lmCNS.unigramsFor(key: key)
|
||||
}
|
||||
|
||||
if isSymbolEnabled {
|
||||
rawAllUnigrams += lmUserSymbols.unigramsFor(key: key)
|
||||
rawAllUnigrams += vChewing.LMInstantiator.lmSymbols.unigramsFor(key: key)
|
||||
rawAllUnigrams += vChewingLM.LMInstantiator.lmSymbols.unigramsFor(key: key)
|
||||
}
|
||||
|
||||
// 新增與日期、時間、星期有關的單元圖資料
|
||||
rawAllUnigrams.append(contentsOf: queryDateTimeUnigrams(with: key))
|
||||
|
||||
// 準備過濾清單。因為我們在 Swift 使用 NSOrderedSet,所以就不需要統計清單了。
|
||||
var filteredPairs: Set<String> = []
|
||||
var filteredList: Set<String> = []
|
||||
|
||||
// 載入要過濾的 KeyValuePair 清單。
|
||||
for unigram in lmFiltered.unigramsFor(key: key) {
|
||||
filteredPairs.insert(unigram.value)
|
||||
filteredList.insert(unigram.value)
|
||||
}
|
||||
|
||||
return filterAndTransform(
|
||||
unigrams: rawAllUnigrams,
|
||||
filter: filteredPairs
|
||||
)
|
||||
}
|
||||
|
||||
/// 根據給定的索引鍵來確認各個資料庫陣列內是否存在對應的資料。
|
||||
/// - Parameter key: 索引鍵。
|
||||
/// - Returns: 是否在庫。
|
||||
public func hasUnigramsFor(key: String) -> Bool {
|
||||
if key == " " { return true }
|
||||
|
||||
if !lmFiltered.hasUnigramsFor(key: key) {
|
||||
return lmUserPhrases.hasUnigramsFor(key: key) || lmCore.hasUnigramsFor(key: key)
|
||||
}
|
||||
|
||||
return !unigramsFor(key: key).isEmpty
|
||||
}
|
||||
|
||||
public func associatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] {
|
||||
lmAssociates.valuesFor(pair: pair)
|
||||
}
|
||||
|
||||
public func hasAssociatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> Bool {
|
||||
lmAssociates.hasValuesFor(pair: pair)
|
||||
}
|
||||
|
||||
// MARK: - 核心函式(對內)
|
||||
|
||||
/// 給定單元圖原始結果陣列,經過語彙過濾處理+置換處理+去重複處理之後,給出單元圖結果陣列。
|
||||
/// - Parameters:
|
||||
/// - unigrams: 傳入的單元圖原始結果陣列。
|
||||
/// - filteredPairs: 傳入的要過濾掉的鍵值配對陣列。
|
||||
/// - Returns: 經過語彙過濾處理+置換處理+去重複處理的單元圖結果陣列。
|
||||
func filterAndTransform(
|
||||
unigrams: [Megrez.Unigram],
|
||||
filter filteredPairs: Set<String>
|
||||
) -> [Megrez.Unigram] {
|
||||
var results: [Megrez.Unigram] = []
|
||||
var insertedPairs: Set<String> = []
|
||||
for unigram in unigrams {
|
||||
var theValue: String = unigram.value
|
||||
if filteredPairs.contains(theValue) { continue }
|
||||
if isPhraseReplacementEnabled {
|
||||
let replacement = lmReplacements.valuesFor(key: theValue)
|
||||
if !replacement.isEmpty { theValue = replacement }
|
||||
// 提前處理語彙置換
|
||||
if isPhraseReplacementEnabled {
|
||||
for i in 0..<rawAllUnigrams.count {
|
||||
let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value)
|
||||
guard !newValue.isEmpty else { continue }
|
||||
rawAllUnigrams[i].value = newValue
|
||||
}
|
||||
if insertedPairs.contains(theValue) { continue }
|
||||
results.append(Megrez.Unigram(value: theValue, score: unigram.score))
|
||||
insertedPairs.insert(theValue)
|
||||
}
|
||||
return results
|
||||
|
||||
// 給定過濾清單,讓單元圖陣列自我過濾。
|
||||
// 在此基礎之上,對於相同詞值的多個單元圖,僅保留權重最大者。
|
||||
rawAllUnigrams.consolidate(filter: filteredList)
|
||||
return rawAllUnigrams
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,10 +7,11 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
|
||||
// MARK: - 日期時間便捷輸入功能
|
||||
|
||||
extension vChewing.LMInstantiator {
|
||||
extension vChewingLM.LMInstantiator {
|
||||
func queryDateTimeUnigrams(with key: String = "") -> [Megrez.Unigram] {
|
||||
if !["ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ", "ㄕˊ-ㄐㄧㄢ", "ㄒㄧㄥ-ㄑㄧ", "ㄒㄧㄥ-ㄑㄧˊ"].contains(key) { return .init() }
|
||||
var results = [Megrez.Unigram]()
|
||||
|
@ -18,7 +19,7 @@ extension vChewing.LMInstantiator {
|
|||
let currentDate = Date()
|
||||
var delta = DateComponents()
|
||||
let thisYear = Calendar.current.dateComponents([.year], from: currentDate).year ?? 2018
|
||||
delta.year = max(min(mgrPrefs.deltaOfCalendarYears, 0), thisYear * -1)
|
||||
delta.year = max(min(deltaOfCalendarYears, 0), thisYear * -1)
|
||||
let currentDateShortened = Calendar.current.date(byAdding: delta, to: currentDate)
|
||||
switch key {
|
||||
case "ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ":
|
||||
|
@ -28,7 +29,7 @@ extension vChewing.LMInstantiator {
|
|||
formatterDate2.dateFormat = "yyyy年MM月dd日"
|
||||
let date1 = formatterDate1.string(from: currentDate)
|
||||
let date2 = formatterDate2.string(from: currentDate)
|
||||
var date3 = ChineseConverter.convertArabicNumeralsToChinese(target: date2)
|
||||
var date3 = date2.convertArabicNumeralsToChinese
|
||||
date3 = date3.replacingOccurrences(of: "年〇", with: "年")
|
||||
date3 = date3.replacingOccurrences(of: "月〇", with: "月")
|
||||
results.append(.init(value: date1, score: -94))
|
||||
|
@ -39,7 +40,7 @@ extension vChewing.LMInstantiator {
|
|||
dateAlt1.regReplace(pattern: #"^0+"#)
|
||||
var dateAlt2: String = formatterDate2.string(from: currentDateShortened)
|
||||
dateAlt2.regReplace(pattern: #"^0+"#)
|
||||
var dateAlt3 = ChineseConverter.convertArabicNumeralsToChinese(target: dateAlt2)
|
||||
var dateAlt3 = dateAlt2.convertArabicNumeralsToChinese
|
||||
dateAlt3 = dateAlt3.replacingOccurrences(of: "年〇", with: "年")
|
||||
dateAlt3 = dateAlt3.replacingOccurrences(of: "月〇", with: "月")
|
||||
results.append(.init(value: dateAlt1, score: -97))
|
||||
|
@ -51,8 +52,8 @@ extension vChewing.LMInstantiator {
|
|||
let formatterTime2 = DateFormatter()
|
||||
let formatterTime3 = DateFormatter()
|
||||
formatterTime1.dateFormat = "HH:mm"
|
||||
formatterTime2.dateFormat = IME.currentInputMode == .imeModeCHS ? "HH点mm分" : "HH點mm分"
|
||||
formatterTime3.dateFormat = IME.currentInputMode == .imeModeCHS ? "HH时mm分" : "HH時mm分"
|
||||
formatterTime2.dateFormat = isCHS ? "HH点mm分" : "HH點mm分"
|
||||
formatterTime3.dateFormat = isCHS ? "HH时mm分" : "HH時mm分"
|
||||
let time1 = formatterTime1.string(from: currentDate)
|
||||
let time2 = formatterTime2.string(from: currentDate)
|
||||
let time3 = formatterTime3.string(from: currentDate)
|
||||
|
@ -92,3 +93,22 @@ extension String {
|
|||
} catch { return }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Date Time Language Conversion Extension
|
||||
|
||||
private let tableMappingArabicNumeralsToChinese: [String: String] = [
|
||||
"0": "〇", "1": "一", "2": "二", "3": "三", "4": "四", "5": "五", "6": "六", "7": "七", "8": "八", "9": "九",
|
||||
]
|
||||
|
||||
extension String {
|
||||
/// 將給定的字串當中的阿拉伯數字轉為漢語小寫,逐字轉換。
|
||||
/// - Parameter target: 要進行轉換操作的對象,會直接修改該對象。
|
||||
fileprivate var convertArabicNumeralsToChinese: String {
|
||||
var target = self
|
||||
for key in tableMappingArabicNumeralsToChinese.keys {
|
||||
guard let result = tableMappingArabicNumeralsToChinese[key] else { continue }
|
||||
target = target.replacingOccurrences(of: key, with: result)
|
||||
}
|
||||
return target
|
||||
}
|
||||
}
|
|
@ -7,9 +7,11 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
import PinyinPhonaConverter
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
extension vChewingLM {
|
||||
@frozen public struct LMAssociates {
|
||||
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
|
||||
var strData: String = ""
|
||||
|
@ -32,7 +34,9 @@ extension vChewing {
|
|||
}
|
||||
let arrTarget = target.dropLast().dropFirst().split(separator: ",")
|
||||
guard arrTarget.count == 2 else { return target }
|
||||
return "(\(Tekkon.cnvHanyuPinyinToPhona(target: String(arrTarget[0]).lowercased())),\(arrTarget[1]))"
|
||||
var arrTarget0 = String(arrTarget[0]).lowercased()
|
||||
arrTarget0.converToPhonabets()
|
||||
return "(\(arrTarget0),\(arrTarget[1]))"
|
||||
}
|
||||
|
||||
@discardableResult public mutating func open(_ path: String) -> Bool {
|
||||
|
@ -59,8 +63,8 @@ extension vChewing {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
IME.prtDebugIntel("\(error)")
|
||||
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
|
||||
vCLog("\(error)")
|
||||
vCLog("↑ Exception happened when reading data at: \(path).")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -73,11 +77,6 @@ extension vChewing {
|
|||
}
|
||||
}
|
||||
|
||||
public func dump() {
|
||||
// We remove this function in order to reduce out maintenance workload.
|
||||
// This function will be implemented only if further hard-necessity comes.
|
||||
}
|
||||
|
||||
public func valuesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] {
|
||||
var pairs: [String] = []
|
||||
if let arrRangeRecords: [(Range<String.Index>, Int)] = rangeMap[pair.toNGramKey] {
|
|
@ -7,9 +7,11 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
import PinyinPhonaConverter
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
extension vChewingLM {
|
||||
/// 與之前的 LMCore 不同,LMCoreEX 不在辭典內記錄實體,而是記錄 range 範圍。
|
||||
/// 需要資料的時候,直接拿 range 去 strData 取資料。
|
||||
/// 資料記錄原理與上游 C++ 的 ParselessLM 差不多,但用的是 Swift 原生手段。
|
||||
|
@ -20,12 +22,12 @@ extension vChewing {
|
|||
/// 資料庫字串陣列。
|
||||
var strData: String = ""
|
||||
/// 聲明原始檔案內第一、二縱列的內容是否彼此顛倒。
|
||||
var shouldReverse: Bool = false
|
||||
var allowConsolidation: Bool = false
|
||||
var shouldReverse = false
|
||||
var allowConsolidation = false
|
||||
/// 當某一筆資料內的權重資料毀損時,要施加的預設權重。
|
||||
var defaultScore: Double = 0
|
||||
/// 啟用該選項的話,會強制施加預設權重、而無視原始權重資料。
|
||||
var shouldForceDefaultScore: Bool = false
|
||||
var shouldForceDefaultScore = false
|
||||
|
||||
/// 資料陣列內承載的資料筆數。
|
||||
public var count: Int {
|
||||
|
@ -75,15 +77,16 @@ extension vChewing {
|
|||
let neta = strData[$0].split(separator: " ")
|
||||
if neta.count >= 2, String(neta[0]).first != "#" {
|
||||
if !neta[0].isEmpty, !neta[1].isEmpty {
|
||||
let theKey = shouldReverse ? String(neta[1]) : String(neta[0])
|
||||
var theKey = shouldReverse ? String(neta[1]) : String(neta[0])
|
||||
let theValue = $0
|
||||
rangeMap[Tekkon.cnvHanyuPinyinToPhona(target: theKey.lowercased()), default: []].append(theValue)
|
||||
theKey.converToPhonabets()
|
||||
rangeMap[theKey, default: []].append(theValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
IME.prtDebugIntel("\(error)")
|
||||
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
|
||||
vCLog("\(error)")
|
||||
vCLog("↑ Exception happened when reading data at: \(path).")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -112,7 +115,7 @@ extension vChewing {
|
|||
strDump += addline
|
||||
}
|
||||
}
|
||||
IME.prtDebugIntel(strDump)
|
||||
vCLog(strDump)
|
||||
}
|
||||
|
||||
/// 根據給定的讀音索引鍵,來獲取資料庫辭典內的對應資料陣列的字串首尾範圍資料、據此自 strData 取得字串形式的資料、生成單元圖陣列。
|
|
@ -7,8 +7,10 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
extension vChewingLM {
|
||||
/// 與之前的 LMCore 不同,LMCoreNS 直接讀取 plist。
|
||||
/// 這樣一來可以節省在舊 mac 機種內的資料讀入速度。
|
||||
/// 目前僅針對輸入法原廠語彙資料檔案使用 plist 格式。
|
||||
|
@ -18,13 +20,13 @@ extension vChewing {
|
|||
/// 【已作廢】資料庫字串陣列。在 LMCoreNS 內沒有作用。
|
||||
var strData: String = ""
|
||||
/// 【已作廢】聲明原始檔案內第一、二縱列的內容是否彼此顛倒。
|
||||
var shouldReverse: Bool = false
|
||||
var shouldReverse = false
|
||||
/// 請且僅請對使用者語言模組啟用該參數:是否自動整理格式。
|
||||
var allowConsolidation: Bool = false
|
||||
var allowConsolidation = false
|
||||
/// 當某一筆資料內的權重資料毀損時,要施加的預設權重。
|
||||
var defaultScore: Double = 0
|
||||
/// 啟用該選項的話,會強制施加預設權重、而無視原始權重資料。
|
||||
var shouldForceDefaultScore: Bool = false
|
||||
var shouldForceDefaultScore = false
|
||||
|
||||
/// 資料陣列內承載的資料筆數。
|
||||
public var count: Int {
|
||||
|
@ -70,7 +72,7 @@ extension vChewing {
|
|||
try PropertyListSerialization.propertyList(from: rawData, format: nil) as? [String: [Data]] ?? .init()
|
||||
rangeMap = rawPlist
|
||||
} catch {
|
||||
IME.prtDebugIntel("↑ Exception happened when reading plist file at: \(path).")
|
||||
vCLog("↑ Exception happened when reading plist file at: \(path).")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -105,7 +107,7 @@ extension vChewing {
|
|||
strDump += "\(cnvPhonabetToASCII(theKey)) \(theValue) \(theScore)\n"
|
||||
}
|
||||
}
|
||||
IME.prtDebugIntel(strDump)
|
||||
vCLog(strDump)
|
||||
}
|
||||
|
||||
/// 根據給定的讀音索引鍵,來獲取資料庫辭典內的對應資料陣列的 UTF8 資料、就地分析、生成單元圖陣列。
|
|
@ -8,8 +8,9 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
extension vChewingLM {
|
||||
@frozen public struct LMPlainBopomofo {
|
||||
var rangeMap: [String: String] = [:]
|
||||
|
||||
|
@ -36,8 +37,8 @@ extension vChewing {
|
|||
try PropertyListSerialization.propertyList(from: rawData, format: nil) as? [String: String] ?? .init()
|
||||
rangeMap = rawPlist
|
||||
} catch {
|
||||
IME.prtDebugIntel("\(error)")
|
||||
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
|
||||
vCLog("\(error)")
|
||||
vCLog("↑ Exception happened when reading data at: \(path).")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -50,15 +51,10 @@ extension vChewing {
|
|||
}
|
||||
}
|
||||
|
||||
public func dump() {
|
||||
// We remove this function in order to reduce out maintenance workload.
|
||||
// This function will be implemented only if further hard-necessity comes.
|
||||
}
|
||||
|
||||
public func valuesFor(key: String) -> [String] {
|
||||
var pairs: [String] = []
|
||||
if let arrRangeRecords: String = rangeMap[key] {
|
||||
pairs.append(contentsOf: arrRangeRecords.charComponents)
|
||||
pairs.append(contentsOf: arrRangeRecords.map { String($0) })
|
||||
}
|
||||
var set = Set<String>()
|
||||
return pairs.filter { set.insert($0).inserted }
|
|
@ -7,10 +7,10 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
@frozen public struct LMReplacments {
|
||||
extension vChewingLM {
|
||||
@frozen public struct LMReplacements {
|
||||
var rangeMap: [String: Range<String.Index>] = [:]
|
||||
var strData: String = ""
|
||||
|
||||
|
@ -48,8 +48,8 @@ extension vChewing {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
IME.prtDebugIntel("\(error)")
|
||||
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
|
||||
vCLog("\(error)")
|
||||
vCLog("↑ Exception happened when reading data at: \(path).")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ extension vChewing {
|
|||
for entry in rangeMap {
|
||||
strDump += strData[entry.value] + "\n"
|
||||
}
|
||||
IME.prtDebugIntel(strDump)
|
||||
vCLog(strDump)
|
||||
}
|
||||
|
||||
public func valuesFor(key: String) -> String {
|
|
@ -1,5 +1,5 @@
|
|||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// Refactored from the Cpp version of this class by Mengjuei Hsieh (MIT License).
|
||||
// Refactored from the Cpp version of this class by Lukhnos Liu (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
// ... with NTL restriction stating that:
|
||||
|
@ -8,8 +8,10 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
import Shared
|
||||
|
||||
extension vChewing {
|
||||
extension vChewingLM {
|
||||
public class LMUserOverride {
|
||||
// MARK: - Main
|
||||
|
||||
|
@ -18,12 +20,14 @@ extension vChewing {
|
|||
var mutLRUList: [KeyObservationPair] = []
|
||||
var mutLRUMap: [String: KeyObservationPair] = [:]
|
||||
let kDecayThreshold: Double = 1.0 / 1_048_576.0 // 衰減二十次之後差不多就失效了。
|
||||
var fileSaveLocationURL: URL
|
||||
|
||||
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6 小時半衰一次,能持續不到六天的記憶。
|
||||
|
||||
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife) {
|
||||
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(
|
||||
|
@ -49,7 +53,7 @@ extension vChewing {
|
|||
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
|
||||
|
||||
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
|
||||
let key: String = vChewing.LMUserOverride.formObservationKey(
|
||||
let key: String = vChewingLM.LMUserOverride.formObservationKey(
|
||||
walkedNodes: walkedAfter, headIndex: targetNodeIndex
|
||||
)
|
||||
guard !key.isEmpty else { return }
|
||||
|
@ -64,7 +68,7 @@ extension vChewing {
|
|||
) -> Suggestion {
|
||||
var headIndex = 0
|
||||
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
|
||||
let key = vChewing.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
|
||||
let key = vChewingLM.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
|
||||
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.key)
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +76,7 @@ extension vChewing {
|
|||
|
||||
// MARK: - Private Structures
|
||||
|
||||
extension vChewing.LMUserOverride {
|
||||
extension vChewingLM.LMUserOverride {
|
||||
enum OverrideUnit: CodingKey { case count, timestamp }
|
||||
enum ObservationUnit: CodingKey { case count, overrides }
|
||||
enum KeyObservationPairUnit: CodingKey { case key, observation }
|
||||
|
@ -149,7 +153,7 @@ extension vChewing.LMUserOverride {
|
|||
|
||||
// MARK: - Hash and Dehash the entire UOM data
|
||||
|
||||
extension vChewing.LMUserOverride {
|
||||
extension vChewingLM.LMUserOverride {
|
||||
/// 自 LRU 辭典內移除所有的單元圖。
|
||||
public func bleachUnigrams(saveCallback: @escaping () -> Void) {
|
||||
for key in mutLRUMap.keys {
|
||||
|
@ -174,18 +178,19 @@ extension vChewing.LMUserOverride {
|
|||
let nullData = "{}"
|
||||
try nullData.write(to: fileURL, atomically: false, encoding: .utf8)
|
||||
} catch {
|
||||
IME.prtDebugIntel("UOM Error: Unable to clear data. Details: \(error)")
|
||||
vCLog("UOM Error: Unable to clear data. Details: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
public func saveData(toURL fileURL: URL) {
|
||||
public func saveData(toURL fileURL: URL? = nil) {
|
||||
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 {
|
||||
IME.prtDebugIntel("UOM Error: Unable to save data, abort saving. Details: \(error)")
|
||||
vCLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -196,27 +201,27 @@ extension vChewing.LMUserOverride {
|
|||
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 {
|
||||
IME.prtDebugIntel("UOM Error: Read file content type invalid, abort loading.")
|
||||
vCLog("UOM Error: Read file content type invalid, abort loading.")
|
||||
return
|
||||
}
|
||||
mutLRUMap = jsonResult
|
||||
resetMRUList()
|
||||
} catch {
|
||||
IME.prtDebugIntel("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
|
||||
}
|
||||
}
|
||||
|
||||
public struct Suggestion {
|
||||
var candidates = [(String, Megrez.Unigram)]()
|
||||
var forceHighScoreOverride = false
|
||||
var isEmpty: Bool { candidates.isEmpty }
|
||||
public var candidates = [(String, Megrez.Unigram)]()
|
||||
public var forceHighScoreOverride = false
|
||||
public var isEmpty: Bool { candidates.isEmpty }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
extension vChewing.LMUserOverride {
|
||||
extension vChewingLM.LMUserOverride {
|
||||
private func doObservation(
|
||||
key: String, candidate: String, timestamp: Double, forceHighScoreOverride: Bool,
|
||||
saveCallback: @escaping () -> Void
|
||||
|
@ -235,7 +240,7 @@ extension vChewing.LMUserOverride {
|
|||
mutLRUMap.removeValue(forKey: mutLRUList[mutLRUList.endIndex].key)
|
||||
mutLRUList.removeLast()
|
||||
}
|
||||
IME.prtDebugIntel("UOM: Observation finished with new observation: \(key)")
|
||||
vCLog("UOM: Observation finished with new observation: \(key)")
|
||||
saveCallback()
|
||||
return
|
||||
}
|
||||
|
@ -246,7 +251,7 @@ extension vChewing.LMUserOverride {
|
|||
)
|
||||
mutLRUList.insert(theNeta, at: 0)
|
||||
mutLRUMap[key] = theNeta
|
||||
IME.prtDebugIntel("UOM: Observation finished with existing observation: \(key)")
|
||||
vCLog("UOM: Observation finished with existing observation: \(key)")
|
||||
saveCallback()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// (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 enum vChewingLM {}
|
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 and onwards Lukhnos Liu for upstream contents.
|
||||
Copyright (c) 2022 and onwards The vChewing Project for Megrez-specific changes.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,26 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Megrez",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Megrez",
|
||||
targets: ["Megrez"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Megrez",
|
||||
dependencies: []
|
||||
),
|
||||
.testTarget(
|
||||
name: "MegrezTests",
|
||||
dependencies: ["Megrez"]
|
||||
),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,123 @@
|
|||
# Megrez Engine 天權星引擎
|
||||
|
||||
- Gitee: [Swift](https://gitee.com/vChewing/Megrez) | [C#](https://gitee.com/vChewing/MegrezNT)
|
||||
- GitHub: [Swift](https://github.com/vChewing/Megrez) | [C#](https://github.com/vChewing/MegrezNT)
|
||||
|
||||
> 該引擎已經實裝於基於純 Swift 語言完成的 **威注音輸入法** 內,歡迎好奇者嘗試:[GitHub](https://github.com/vChewing/vChewing-macOS ) | [Gitee](https://gitee.com/vchewing/vChewing-macOS ) 。
|
||||
|
||||
天權星引擎是用來處理輸入法語彙庫的一個模組。該倉庫乃威注音專案的弒神行動(Operation Longinus)的一部分。
|
||||
|
||||
Megrez Engine is a module made for processing lingual data of an input method. This repository is part of Operation Longinus of The vChewing Project.
|
||||
|
||||
## 使用說明
|
||||
|
||||
### §1. 初期化
|
||||
|
||||
在你的 ctlInputMethod (InputMethodController) 或者 KeyHandler 內初期化一份 Megrez.Compositor 組字器副本(這裡將該副本命名為「`compositor`」)。由於 Megrez.Compositor 的型別是 Struct 型別(為了讓 Compositor 可以 deep copy),所以其副本可以用 var 來宣告。
|
||||
|
||||
以 KeyHandler 為例:
|
||||
```swift
|
||||
class KeyHandler {
|
||||
// 先設定好變數
|
||||
var compositor: Megrez.Compositor = .init()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
以 ctlInputMethod 為例:
|
||||
```swift
|
||||
@objc(ctlInputMethod) // 根據 info.plist 內的情況來確定型別的命名
|
||||
class ctlInputMethod: IMKInputController {
|
||||
// 先設定好變數
|
||||
var compositor: Megrez.Compositor = .init()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
由於 Swift 會在某個大副本(KeyHandler 或者 ctlInputMethod 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Megrez.Compositor 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所對接的語言模組型別、以及其可以允許的最大詞長。
|
||||
|
||||
這裡就需要在 init() 時使用參數:
|
||||
```swift
|
||||
/// 組字器。
|
||||
/// - Parameters:
|
||||
/// - lm: 語言模型。可以是任何基於 Megrez.LangModel 的衍生型別。
|
||||
/// - length: 指定該組字器內可以允許的最大詞長,預設為 10 字。
|
||||
/// - separator: 多字讀音鍵當中用以分割漢字讀音的記號,預設為空。
|
||||
var compositor: Megrez.Compositor = .init(lm: lmTest, length: 13, separator: "-")
|
||||
```
|
||||
|
||||
### §2. 使用範例
|
||||
|
||||
請結合 MegrezTests.swift 檔案來學習。這裡只是給個概述。
|
||||
|
||||
#### // 1. 準備用作語言模型的專用型別
|
||||
|
||||
首先,Megrez 內建的 LangModel 型別是遠遠不夠用的,只能說是個類似於 protocol 一樣的存在。你需要自己單獨寫一個新的衍生型別:
|
||||
|
||||
```swift
|
||||
class ExampleLM: Megrez.LangModel {
|
||||
...
|
||||
override func unigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
這個型別需要下述兩個函式能夠針對給定的鍵回饋對應的資料值、或其存無狀態:
|
||||
- unigramsFor(key: String) -> [Megrez.Unigram]
|
||||
- hasUnigramsFor(key: String) -> Bool
|
||||
|
||||
MegrezTests.swift 檔案內的 SimpleLM 可以作為範例。
|
||||
|
||||
至於該檔案內的 FiniteStateMachine 則是一個「一次性讀取檔案/大字串且分析資料」的範例。
|
||||
|
||||
如果需要更實戰的範例的話,可以洽威注音專案的倉庫內的 LMInstantiator.swift 及其關聯檔案。
|
||||
|
||||
#### // 2. 怎樣與 compositor 互動:
|
||||
|
||||
這裡只講幾個常用函式:
|
||||
|
||||
- 游標位置 `compositor.cursorIndex` 是可以賦值與取值的動態變數,且會在賦值內容為超出位置範圍的數值時自動修正。初期值為 0。
|
||||
- `compositor.insertKey("gao1")` 可以在當前的游標位置插入讀音「gao1」。
|
||||
- `compositor.dropKey(direction: .front)` 的作用是:朝著往文字輸入方向、砍掉一個與游標相鄰的讀音。反之,`dropKey(direction: .rear)` 則朝著與文字輸入方向相反的方向、砍掉一個與游標相鄰的讀音。
|
||||
- 在威注音的術語體系當中,「文字輸入方向」為向前(Front)、與此相反的方向為向後(Rear)。
|
||||
- `compositor.overrideCandidate(.init(key: "讀音", value: "候選字"), at: 游標位置, overrideType: 覆寫模式)` 用來根據輸入法選中的候選字詞、據此更新當前游標位置選中的候選字詞節點當中的候選字詞。
|
||||
|
||||
輸入完內容之後,可以聲明一個用來接收結果的變數:
|
||||
|
||||
```swift
|
||||
/// 對已給定的軌格按照給定的位置與條件進行正向爬軌。
|
||||
var walked = compositor.walk()
|
||||
```
|
||||
|
||||
MegrezTests.swift 是輸入了很多內容之後再 walk 的。實際上一款輸入法會在你每次插入讀音或刪除讀音的時候都重新 walk。那些處於候選字詞鎖定狀態的節點不會再受到之後的 walk 的行為的影響,但除此之外的節點會因為每次 walk 而可能各自的候選字詞會出現自動變化。如果給了 nodesLimit 一個非零的數值的話,則 walk 的範圍外的節點不會受到影響。
|
||||
|
||||
walk 之後的取值的方法及利用方法可以有很多種。這裡有其中的一個:
|
||||
|
||||
```swift
|
||||
var composed: [String] = walked.map(\.value)
|
||||
print(composed)
|
||||
```
|
||||
|
||||
類似於:
|
||||
|
||||
```swift
|
||||
for phrase in walked {
|
||||
composed.append(phrase.value)
|
||||
}
|
||||
print(composed)
|
||||
```
|
||||
|
||||
上述 print 結果就是 compositor 目前的組句,是這種陣列格式(以吳宗憲的詩句為例):
|
||||
```swift
|
||||
["八月", "中秋", "山林", "涼", "風吹", "大地", "草枝", "擺"]
|
||||
```
|
||||
|
||||
自己看 MegrezTests.swift 慢慢研究吧。
|
||||
|
||||
## 著作權 (Credits)
|
||||
|
||||
- Swiftified and further development by (c) 2022 and onwards The vChewing Project (MIT License).
|
||||
- Swift programmer: Shiki Suen
|
||||
- Was initially rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
|
|
@ -44,13 +44,13 @@ extension Megrez {
|
|||
public var isEmpty: Bool { spans.isEmpty && keys.isEmpty }
|
||||
|
||||
/// 該組字器的索引鍵陣列。
|
||||
private(set) var keys = [String]()
|
||||
public private(set) var keys = [String]()
|
||||
/// 該組字器的幅位陣列。
|
||||
private(set) var spans = [Span]()
|
||||
public private(set) var spans = [Span]()
|
||||
/// 該組字器所使用的語言模型(被 LangModelRanked 所封裝)。
|
||||
private(set) var langModel: LangModelRanked
|
||||
public var langModel: LangModelRanked
|
||||
/// 允許查詢當前游標位置屬於第幾個幅位座標(從 0 開始算)。
|
||||
private(set) var cursorRegionMap: [Int: Int] = .init()
|
||||
public private(set) var cursorRegionMap: [Int: Int] = .init()
|
||||
|
||||
/// 初期化一個組字器。
|
||||
/// - Parameter langModel: 要對接的語言模組。
|
|
@ -7,7 +7,7 @@ extension Megrez.Compositor {
|
|||
/// 幅位乃指一組共享起點的節點。
|
||||
public class Span {
|
||||
private var nodes: [Node?] = []
|
||||
private(set) var maxLength = 0
|
||||
public private(set) var maxLength = 0
|
||||
private var maxSpanLength: Int { Megrez.Compositor.maxSpanLength }
|
||||
public init() {
|
||||
clear()
|
||||
|
@ -57,7 +57,7 @@ extension Megrez.Compositor {
|
|||
|
||||
public func nodeOf(length: Int) -> Node? {
|
||||
guard (1...maxSpanLength).contains(length) else { return nil }
|
||||
return nodes[length - 1] ?? nil
|
||||
return nodes[length - 1]
|
||||
}
|
||||
}
|
||||
|
|
@ -34,11 +34,11 @@ extension Megrez.Compositor {
|
|||
/// 數(比如野獸常數),以讓「c」更容易單獨被選中。
|
||||
public var overridingScore: Double = 114_514
|
||||
|
||||
private(set) var key: String
|
||||
private(set) var keyArray: [String]
|
||||
private(set) var spanLength: Int
|
||||
private(set) var unigrams: [Megrez.Unigram]
|
||||
private(set) var currentUnigramIndex: Int = 0 {
|
||||
public private(set) var key: String
|
||||
public private(set) var keyArray: [String]
|
||||
public private(set) var spanLength: Int
|
||||
public private(set) var unigrams: [Megrez.Unigram]
|
||||
public private(set) var currentUnigramIndex: Int = 0 {
|
||||
didSet { currentUnigramIndex = min(max(0, currentUnigramIndex), unigrams.count - 1) }
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ extension Megrez.Compositor {
|
|||
hasher.combine(overrideType)
|
||||
}
|
||||
|
||||
private(set) var overrideType: Node.OverrideType
|
||||
public private(set) var overrideType: Node.OverrideType
|
||||
|
||||
public static func == (lhs: Node, rhs: Node) -> Bool {
|
||||
lhs.key == rhs.key && lhs.spanLength == rhs.spanLength
|
|
@ -38,3 +38,20 @@ extension Megrez {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Array Extensions.
|
||||
|
||||
extension Array where Element == Megrez.Unigram {
|
||||
/// 給定過濾清單,讓單元圖陣列自我過濾。
|
||||
/// 在此基礎之上,對於相同詞值的多個單元圖,僅保留權重最大者。
|
||||
public mutating func consolidate(filter theFilter: Set<String> = .init()) {
|
||||
var inserted: [String: Double] = [:]
|
||||
var insertedArray: [Megrez.Unigram] = []
|
||||
for neta in filter({ !theFilter.contains($0.value) }) {
|
||||
if let existed = inserted[neta.value], existed >= neta.score { continue }
|
||||
inserted[neta.value] = neta.score
|
||||
insertedArray.append(neta)
|
||||
}
|
||||
self = insertedArray
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
|
||||
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
|
||||
import Megrez
|
||||
|
||||
// MARK: - 用以測試的語言模型(簡單範本型)
|
||||
|
||||
class SimpleLM: LangModelProtocol {
|
||||
var mutDatabase: [String: [Megrez.Unigram]] = [:]
|
||||
init(input: String, swapKeyValue: Bool = false) {
|
||||
let sstream = input.components(separatedBy: "\n")
|
||||
for line in sstream {
|
||||
if line.isEmpty || line.hasPrefix("#") {
|
||||
continue
|
||||
}
|
||||
let linestream = line.split(separator: " ")
|
||||
let col0 = String(linestream[0])
|
||||
let col1 = String(linestream[1])
|
||||
let col2 = Double(linestream[2]) ?? 0.0
|
||||
var u = Megrez.Unigram(value: swapKeyValue ? col0 : col1, score: 0)
|
||||
u.score = col2
|
||||
mutDatabase[swapKeyValue ? col1 : col0, default: []].append(u)
|
||||
}
|
||||
}
|
||||
|
||||
func unigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
if let f = mutDatabase[key] {
|
||||
return f
|
||||
} else {
|
||||
return [Megrez.Unigram]().sorted { $0.score > $1.score }
|
||||
}
|
||||
}
|
||||
|
||||
func hasUnigramsFor(key: String) -> Bool {
|
||||
mutDatabase.keys.contains(key)
|
||||
}
|
||||
}
|
||||
|
||||
class MockLM: LangModelProtocol {
|
||||
func unigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
[Megrez.Unigram(value: key, score: -1)]
|
||||
}
|
||||
|
||||
func hasUnigramsFor(key: String) -> Bool {
|
||||
!key.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 用以測試的詞頻數據
|
||||
|
||||
public let strStressData = #"""
|
||||
yi1 一 -2.08170692
|
||||
yi1-yi1 一一 -4.38468400
|
||||
|
||||
"""#
|
||||
|
||||
public let strEmojiSampleData = #"""
|
||||
gao1 高 -2.9396
|
||||
re4 熱 -3.6024
|
||||
gao1re4 高熱 -6.526
|
||||
huo3 火 -3.6966
|
||||
huo3 🔥 -8
|
||||
yan4 焰 -5.4466
|
||||
huo3yan4 火焰 -5.6231
|
||||
huo3yan4 🔥 -8
|
||||
wei2 危 -3.9832
|
||||
xian3 險 -3.7810
|
||||
wei2xian3 危險 -4.2623
|
||||
mi4feng1 蜜蜂 -3.6231
|
||||
mi4 蜜 -4.6231
|
||||
feng1 蜂 -4.6231
|
||||
feng1 🐝 -11
|
||||
mi4feng1 🐝 -11
|
||||
|
||||
"""#
|
||||
|
||||
public let strSampleData = #"""
|
||||
#
|
||||
# 下述詞頻資料取自 libTaBE 資料庫 (http://sourceforge.net/projects/libtabe/)
|
||||
# (2002 最終版). 該專案於 1999 年由 Pai-Hsiang Hsiao 發起、以 BSD 授權發行。
|
||||
#
|
||||
ni3 你 -6.000000 // Non-LibTaBE
|
||||
zhe4 這 -6.000000 // Non-LibTaBE
|
||||
yang4 樣 -6.000000 // Non-LibTaBE
|
||||
si1 絲 -9.495858
|
||||
si1 思 -9.006414
|
||||
si1 私 -99.000000
|
||||
si1 斯 -8.091803
|
||||
si1 司 -99.000000
|
||||
si1 嘶 -13.513987
|
||||
si1 撕 -12.259095
|
||||
gao1 高 -7.171551
|
||||
ke1 顆 -10.574273
|
||||
ke1 棵 -11.504072
|
||||
ke1 刻 -10.450457
|
||||
ke1 科 -7.171052
|
||||
ke1 柯 -99.000000
|
||||
gao1 膏 -11.928720
|
||||
gao1 篙 -13.624335
|
||||
gao1 糕 -12.390804
|
||||
de5 的 -3.516024
|
||||
di2 的 -3.516024
|
||||
di4 的 -3.516024
|
||||
zhong1 中 -5.809297
|
||||
de5 得 -7.427179
|
||||
gong1 共 -8.381971
|
||||
gong1 供 -8.501463
|
||||
ji4 既 -99.000000
|
||||
jin1 今 -8.034095
|
||||
gong1 紅 -8.858181
|
||||
ji4 際 -7.608341
|
||||
ji4 季 -99.000000
|
||||
jin1 金 -7.290109
|
||||
ji4 騎 -10.939895
|
||||
zhong1 終 -99.000000
|
||||
ji4 記 -99.000000
|
||||
ji4 寄 -99.000000
|
||||
jin1 斤 -99.000000
|
||||
ji4 繼 -9.715317
|
||||
ji4 計 -7.926683
|
||||
ji4 暨 -8.373022
|
||||
zhong1 鐘 -9.877580
|
||||
jin1 禁 -10.711079
|
||||
gong1 公 -7.877973
|
||||
gong1 工 -7.822167
|
||||
gong1 攻 -99.000000
|
||||
gong1 功 -99.000000
|
||||
gong1 宮 -99.000000
|
||||
zhong1 鍾 -9.685671
|
||||
ji4 繫 -10.425662
|
||||
gong1 弓 -99.000000
|
||||
gong1 恭 -99.000000
|
||||
ji4 劑 -8.888722
|
||||
ji4 祭 -10.204425
|
||||
jin1 浸 -11.378321
|
||||
zhong1 盅 -99.000000
|
||||
ji4 忌 -99.000000
|
||||
ji4 技 -8.450826
|
||||
jin1 筋 -11.074890
|
||||
gong1 躬 -99.000000
|
||||
ji4 冀 -12.045357
|
||||
zhong1 忠 -99.000000
|
||||
ji4 妓 -99.000000
|
||||
ji4 濟 -9.517568
|
||||
ji4 薊 -12.021587
|
||||
jin1 巾 -99.000000
|
||||
jin1 襟 -12.784206
|
||||
nian2 年 -6.086515
|
||||
jiang3 講 -9.164384
|
||||
jiang3 獎 -8.690941
|
||||
jiang3 蔣 -10.127828
|
||||
nian2 黏 -11.336864
|
||||
nian2 粘 -11.285740
|
||||
jiang3 槳 -12.492933
|
||||
gong1si1 公司 -6.299461
|
||||
ke1ji4 科技 -6.736613
|
||||
ji4gong1 濟公 -13.336653
|
||||
jiang3jin1 獎金 -10.344678
|
||||
nian2zhong1 年終 -11.668947
|
||||
nian2zhong1 年中 -11.373044
|
||||
gao1ke1ji4 高科技 -9.842421
|
||||
zhe4yang4 這樣 -6.000000 // Non-LibTaBE
|
||||
ni3zhe4 你這 -9.000000 // Non-LibTaBE
|
||||
jiao4 教 -3.676169
|
||||
jiao4 較 -3.24869962
|
||||
jiao4yu4 教育 -3.32220565
|
||||
yu4 育 -3.30192952
|
||||
|
||||
"""#
|
|
@ -0,0 +1,521 @@
|
|||
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
|
||||
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
|
||||
import Cocoa
|
||||
import XCTest
|
||||
|
||||
@testable import Megrez
|
||||
|
||||
final class MegrezTests: XCTestCase {
|
||||
func testSpan() throws {
|
||||
let langModel = SimpleLM(input: strSampleData)
|
||||
let span = Megrez.Compositor.Span()
|
||||
let n1 = Megrez.Compositor.Node(keyArray: ["gao1"], spanLength: 1, unigrams: langModel.unigramsFor(key: "gao1"))
|
||||
let n3 = Megrez.Compositor.Node(
|
||||
keyArray: ["gao1ke1ji4"], spanLength: 3, unigrams: langModel.unigramsFor(key: "gao1ke1ji4")
|
||||
)
|
||||
XCTAssertEqual(span.maxLength, 0)
|
||||
span.append(node: n1)
|
||||
XCTAssertEqual(span.maxLength, 1)
|
||||
span.append(node: n3)
|
||||
XCTAssertEqual(span.maxLength, 3)
|
||||
XCTAssertEqual(span.nodeOf(length: 1), n1)
|
||||
XCTAssertEqual(span.nodeOf(length: 2), nil)
|
||||
XCTAssertEqual(span.nodeOf(length: 3), n3)
|
||||
XCTAssertEqual(span.nodeOf(length: Megrez.Compositor.maxSpanLength), nil)
|
||||
span.clear()
|
||||
XCTAssertEqual(span.maxLength, 0)
|
||||
XCTAssertEqual(span.nodeOf(length: 1), nil)
|
||||
XCTAssertEqual(span.nodeOf(length: 2), nil)
|
||||
XCTAssertEqual(span.nodeOf(length: 3), nil)
|
||||
XCTAssertEqual(span.nodeOf(length: Megrez.Compositor.maxSpanLength), nil)
|
||||
|
||||
span.append(node: n1)
|
||||
span.append(node: n3)
|
||||
span.dropNodesOfOrBeyond(length: 2)
|
||||
XCTAssertEqual(span.maxLength, 1)
|
||||
XCTAssertEqual(span.nodeOf(length: 1), n1)
|
||||
XCTAssertEqual(span.nodeOf(length: 2), nil)
|
||||
XCTAssertEqual(span.nodeOf(length: 3), nil)
|
||||
span.dropNodesOfOrBeyond(length: 1)
|
||||
XCTAssertEqual(span.maxLength, 0)
|
||||
XCTAssertEqual(span.nodeOf(length: 1), nil)
|
||||
let n114514 = Megrez.Compositor.Node(spanLength: 114_514)
|
||||
XCTAssertFalse(span.append(node: n114514))
|
||||
XCTAssertNil(span.nodeOf(length: 0))
|
||||
XCTAssertNil(span.nodeOf(length: Megrez.Compositor.maxSpanLength + 1))
|
||||
}
|
||||
|
||||
func testRankedLangModel() throws {
|
||||
class TestLM: LangModelProtocol {
|
||||
func hasUnigramsFor(key: String) -> Bool { key == "foo" }
|
||||
func unigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
key == "foo"
|
||||
? [.init(value: "middle", score: -5), .init(value: "highest", score: -2), .init(value: "lowest", score: -10)]
|
||||
: .init()
|
||||
}
|
||||
}
|
||||
|
||||
let lmRanked = Megrez.Compositor.LangModelRanked(withLM: TestLM())
|
||||
XCTAssertTrue(lmRanked.hasUnigramsFor(key: "foo"))
|
||||
XCTAssertFalse(lmRanked.hasUnigramsFor(key: "bar"))
|
||||
XCTAssertTrue(lmRanked.unigramsFor(key: "bar").isEmpty)
|
||||
let unigrams = lmRanked.unigramsFor(key: "foo")
|
||||
XCTAssertEqual(unigrams.count, 3)
|
||||
XCTAssertEqual(unigrams[0].value, "highest")
|
||||
XCTAssertEqual(unigrams[0].score, -2)
|
||||
XCTAssertEqual(unigrams[1].value, "middle")
|
||||
XCTAssertEqual(unigrams[1].score, -5)
|
||||
XCTAssertEqual(unigrams[2].value, "lowest")
|
||||
XCTAssertEqual(unigrams[2].score, -10)
|
||||
}
|
||||
|
||||
func testCompositor_BasicTests() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
XCTAssertEqual(compositor.separator, Megrez.Compositor.kDefaultSeparator)
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertEqual(compositor.length, 0)
|
||||
|
||||
compositor.insertKey("a")
|
||||
XCTAssertEqual(compositor.cursor, 1)
|
||||
XCTAssertEqual(compositor.length, 1)
|
||||
XCTAssertEqual(compositor.spans.count, 1)
|
||||
XCTAssertEqual(compositor.spans[0].maxLength, 1)
|
||||
guard let zeroNode = compositor.spans[0].nodeOf(length: 1) else {
|
||||
print("fuckme")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(zeroNode.key, "a")
|
||||
|
||||
compositor.dropKey(direction: .rear)
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertEqual(compositor.spans.count, 0)
|
||||
}
|
||||
|
||||
func testCompositor_InvalidOperations() throws {
|
||||
class TestLM: LangModelProtocol {
|
||||
func hasUnigramsFor(key: String) -> Bool { key == "foo" }
|
||||
func unigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
key == "foo" ? [.init(value: "foo", score: -1)] : .init()
|
||||
}
|
||||
}
|
||||
|
||||
var compositor = Megrez.Compositor(with: TestLM())
|
||||
compositor.separator = ";"
|
||||
XCTAssertFalse(compositor.insertKey("bar"))
|
||||
XCTAssertFalse(compositor.insertKey(""))
|
||||
XCTAssertFalse(compositor.insertKey(""))
|
||||
XCTAssertFalse(compositor.dropKey(direction: .rear))
|
||||
XCTAssertFalse(compositor.dropKey(direction: .front))
|
||||
|
||||
XCTAssertTrue(compositor.insertKey("foo"))
|
||||
XCTAssertTrue(compositor.dropKey(direction: .rear))
|
||||
XCTAssertEqual(compositor.length, 0)
|
||||
XCTAssertTrue(compositor.insertKey("foo"))
|
||||
compositor.cursor = 0
|
||||
XCTAssertTrue(compositor.dropKey(direction: .front))
|
||||
XCTAssertEqual(compositor.length, 0)
|
||||
}
|
||||
|
||||
func testCompositor_DeleteToTheFrontOfCursor() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.insertKey("a")
|
||||
compositor.cursor = 0
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertEqual(compositor.length, 1)
|
||||
XCTAssertEqual(compositor.spans.count, 1)
|
||||
XCTAssertFalse(compositor.dropKey(direction: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertEqual(compositor.length, 1)
|
||||
XCTAssertTrue(compositor.dropKey(direction: .front))
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertEqual(compositor.length, 0)
|
||||
XCTAssertEqual(compositor.spans.count, 0)
|
||||
}
|
||||
|
||||
func testCompositor_MultipleSpans() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.separator = ";"
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
XCTAssertEqual(compositor.cursor, 3)
|
||||
XCTAssertEqual(compositor.length, 3)
|
||||
XCTAssertEqual(compositor.spans.count, 3)
|
||||
XCTAssertEqual(compositor.spans[0].maxLength, 3)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;b;c")
|
||||
XCTAssertEqual(compositor.spans[1].maxLength, 2)
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "b")
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "b;c")
|
||||
XCTAssertEqual(compositor.spans[2].maxLength, 1)
|
||||
XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.key, "c")
|
||||
}
|
||||
|
||||
func testCompositor_SpanDeletionFromFront() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.separator = ";"
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
XCTAssertFalse(compositor.dropKey(direction: .front))
|
||||
XCTAssertTrue(compositor.dropKey(direction: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 2)
|
||||
XCTAssertEqual(compositor.length, 2)
|
||||
XCTAssertEqual(compositor.spans.count, 2)
|
||||
XCTAssertEqual(compositor.spans[0].maxLength, 2)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b")
|
||||
XCTAssertEqual(compositor.spans[1].maxLength, 1)
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "b")
|
||||
}
|
||||
|
||||
func testCompositor_SpanDeletionFromMiddle() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.separator = ";"
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
compositor.cursor = 2
|
||||
|
||||
XCTAssertTrue(compositor.dropKey(direction: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 1)
|
||||
XCTAssertEqual(compositor.length, 2)
|
||||
XCTAssertEqual(compositor.spans.count, 2)
|
||||
XCTAssertEqual(compositor.spans[0].maxLength, 2)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c")
|
||||
XCTAssertEqual(compositor.spans[1].maxLength, 1)
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c")
|
||||
|
||||
compositor.clear()
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
compositor.cursor = 1
|
||||
|
||||
XCTAssertTrue(compositor.dropKey(direction: .front))
|
||||
XCTAssertEqual(compositor.cursor, 1)
|
||||
XCTAssertEqual(compositor.length, 2)
|
||||
XCTAssertEqual(compositor.spans.count, 2)
|
||||
XCTAssertEqual(compositor.spans[0].maxLength, 2)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c")
|
||||
XCTAssertEqual(compositor.spans[1].maxLength, 1)
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c")
|
||||
}
|
||||
|
||||
func testCompositor_SpanDeletionFromRear() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.separator = ";"
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
compositor.cursor = 0
|
||||
|
||||
XCTAssertFalse(compositor.dropKey(direction: .rear))
|
||||
XCTAssertTrue(compositor.dropKey(direction: .front))
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertEqual(compositor.length, 2)
|
||||
XCTAssertEqual(compositor.spans.count, 2)
|
||||
XCTAssertEqual(compositor.spans[0].maxLength, 2)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "b")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "b;c")
|
||||
XCTAssertEqual(compositor.spans[1].maxLength, 1)
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c")
|
||||
}
|
||||
|
||||
func testCompositor_SpanInsertion() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.separator = ";"
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
compositor.cursor = 1
|
||||
compositor.insertKey("X")
|
||||
|
||||
XCTAssertEqual(compositor.cursor, 2)
|
||||
XCTAssertEqual(compositor.length, 4)
|
||||
XCTAssertEqual(compositor.spans.count, 4)
|
||||
XCTAssertEqual(compositor.spans[0].maxLength, 4)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;X")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;X;b")
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 4)?.key, "a;X;b;c")
|
||||
XCTAssertEqual(compositor.spans[1].maxLength, 3)
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "X")
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "X;b")
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 3)?.key, "X;b;c")
|
||||
XCTAssertEqual(compositor.spans[2].maxLength, 2)
|
||||
XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.key, "b")
|
||||
XCTAssertEqual(compositor.spans[2].nodeOf(length: 2)?.key, "b;c")
|
||||
XCTAssertEqual(compositor.spans[3].maxLength, 1)
|
||||
XCTAssertEqual(compositor.spans[3].nodeOf(length: 1)?.key, "c")
|
||||
}
|
||||
|
||||
func testCompositor_LongGridDeletion() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.separator = ""
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
compositor.insertKey("d")
|
||||
compositor.insertKey("e")
|
||||
compositor.insertKey("f")
|
||||
compositor.insertKey("g")
|
||||
compositor.insertKey("h")
|
||||
compositor.insertKey("i")
|
||||
compositor.insertKey("j")
|
||||
compositor.insertKey("k")
|
||||
compositor.insertKey("l")
|
||||
compositor.insertKey("m")
|
||||
compositor.insertKey("n")
|
||||
compositor.cursor = 7
|
||||
XCTAssertTrue(compositor.dropKey(direction: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 6)
|
||||
XCTAssertEqual(compositor.length, 13)
|
||||
XCTAssertEqual(compositor.spans.count, 13)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef")
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefh")
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 5)?.key, "bcdef")
|
||||
XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefhi")
|
||||
XCTAssertEqual(compositor.spans[2].nodeOf(length: 5)?.key, "cdefh")
|
||||
XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defhij")
|
||||
XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efhijk")
|
||||
XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fhijkl")
|
||||
XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "hijklm")
|
||||
XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "ijklmn")
|
||||
XCTAssertEqual(compositor.spans[8].nodeOf(length: 5)?.key, "jklmn")
|
||||
}
|
||||
|
||||
func testCompositor_LongGridInsertion() throws {
|
||||
var compositor = Megrez.Compositor(with: MockLM())
|
||||
compositor.separator = ""
|
||||
compositor.insertKey("a")
|
||||
compositor.insertKey("b")
|
||||
compositor.insertKey("c")
|
||||
compositor.insertKey("d")
|
||||
compositor.insertKey("e")
|
||||
compositor.insertKey("f")
|
||||
compositor.insertKey("g")
|
||||
compositor.insertKey("h")
|
||||
compositor.insertKey("i")
|
||||
compositor.insertKey("j")
|
||||
compositor.insertKey("k")
|
||||
compositor.insertKey("l")
|
||||
compositor.insertKey("m")
|
||||
compositor.insertKey("n")
|
||||
compositor.cursor = 7
|
||||
compositor.insertKey("X")
|
||||
XCTAssertEqual(compositor.cursor, 8)
|
||||
XCTAssertEqual(compositor.length, 15)
|
||||
XCTAssertEqual(compositor.spans.count, 15)
|
||||
XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef")
|
||||
XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefg")
|
||||
XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefgX")
|
||||
XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defgXh")
|
||||
XCTAssertEqual(compositor.spans[3].nodeOf(length: 5)?.key, "defgX")
|
||||
XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efgXhi")
|
||||
XCTAssertEqual(compositor.spans[4].nodeOf(length: 5)?.key, "efgXh")
|
||||
XCTAssertEqual(compositor.spans[4].nodeOf(length: 4)?.key, "efgX")
|
||||
XCTAssertEqual(compositor.spans[4].nodeOf(length: 3)?.key, "efg")
|
||||
XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fgXhij")
|
||||
XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "gXhijk")
|
||||
XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "Xhijkl")
|
||||
XCTAssertEqual(compositor.spans[8].nodeOf(length: 6)?.key, "hijklm")
|
||||
}
|
||||
|
||||
func testCompositor_StressBench() throws {
|
||||
NSLog("// Stress test preparation begins.")
|
||||
var compositor = Megrez.Compositor(with: SimpleLM(input: strStressData))
|
||||
for _ in 0..<1919 {
|
||||
compositor.insertKey("yi")
|
||||
}
|
||||
NSLog("// Stress test started.")
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
compositor.walk()
|
||||
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
|
||||
NSLog("// Stress test elapsed: \(timeElapsed)s.")
|
||||
}
|
||||
|
||||
func testCompositor_WordSegmentation() throws {
|
||||
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData, swapKeyValue: true))
|
||||
compositor.separator = ""
|
||||
for i in "高科技公司的年終獎金" {
|
||||
compositor.insertKey(String(i))
|
||||
}
|
||||
let result = compositor.walk().0
|
||||
XCTAssertEqual(result.keys, ["高科技", "公司", "的", "年終", "獎金"])
|
||||
}
|
||||
|
||||
func testCompositor_InputTestAndCursorJump() throws {
|
||||
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData))
|
||||
compositor.separator = ""
|
||||
compositor.insertKey("gao1")
|
||||
compositor.walk()
|
||||
compositor.insertKey("ji4")
|
||||
compositor.walk()
|
||||
compositor.cursor = 1
|
||||
compositor.insertKey("ke1")
|
||||
compositor.walk()
|
||||
compositor.cursor = 0
|
||||
compositor.dropKey(direction: .front)
|
||||
compositor.walk()
|
||||
compositor.insertKey("gao1")
|
||||
compositor.walk()
|
||||
compositor.cursor = compositor.length
|
||||
compositor.insertKey("gong1")
|
||||
compositor.walk()
|
||||
compositor.insertKey("si1")
|
||||
compositor.walk()
|
||||
compositor.insertKey("de5")
|
||||
compositor.walk()
|
||||
compositor.insertKey("nian2")
|
||||
compositor.walk()
|
||||
compositor.insertKey("zhong1")
|
||||
compositor.walk()
|
||||
compositor.insertKey("jiang3")
|
||||
compositor.walk()
|
||||
compositor.insertKey("jin1")
|
||||
var result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高科技", "公司", "的", "年中", "獎金"])
|
||||
XCTAssertEqual(compositor.length, 10)
|
||||
compositor.cursor = 7
|
||||
let candidates = compositor.fetchCandidates(at: compositor.cursor).map(\.value)
|
||||
XCTAssertTrue(candidates.contains("年中"))
|
||||
XCTAssertTrue(candidates.contains("年終"))
|
||||
XCTAssertTrue(candidates.contains("中"))
|
||||
XCTAssertTrue(candidates.contains("鍾"))
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("年終", at: 7))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高科技", "公司", "的", "年終", "獎金"])
|
||||
let candidatesBeginAt = compositor.fetchCandidates(at: 3, filter: .beginAt).map(\.value)
|
||||
let candidatesEndAt = compositor.fetchCandidates(at: 3, filter: .endAt).map(\.value)
|
||||
XCTAssertFalse(candidatesBeginAt.contains("濟公"))
|
||||
XCTAssertFalse(candidatesEndAt.contains("公司"))
|
||||
// Test cursor jump.
|
||||
compositor.cursor = 8
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 6)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 5)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 3)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertFalse(compositor.jumpCursorBySpan(to: .rear))
|
||||
XCTAssertEqual(compositor.cursor, 0)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
|
||||
XCTAssertEqual(compositor.cursor, 3)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
|
||||
XCTAssertEqual(compositor.cursor, 5)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
|
||||
XCTAssertEqual(compositor.cursor, 6)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
|
||||
XCTAssertEqual(compositor.cursor, 8)
|
||||
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
|
||||
XCTAssertEqual(compositor.cursor, 10)
|
||||
XCTAssertFalse(compositor.jumpCursorBySpan(to: .front))
|
||||
XCTAssertEqual(compositor.cursor, 10)
|
||||
// Test dumpDOT.
|
||||
let expectedDumpDOT =
|
||||
"digraph {\ngraph [ rankdir=LR ];\nBOS;\nBOS -> 高;\n高;\n高 -> 科;\n高 -> 科技;\nBOS -> 高科技;\n高科技;\n高科技 -> 工;\n高科技 -> 公司;\n科;\n科 -> 際;\n科 -> 濟公;\n科技;\n科技 -> 工;\n科技 -> 公司;\n際;\n際 -> 工;\n際 -> 公司;\n濟公;\n濟公 -> 斯;\n工;\n工 -> 斯;\n公司;\n公司 -> 的;\n斯;\n斯 -> 的;\n的;\n的 -> 年;\n的 -> 年終;\n年;\n年 -> 中;\n年終;\n年終 -> 獎;\n年終 -> 獎金;\n中;\n中 -> 獎;\n中 -> 獎金;\n獎;\n獎 -> 金;\n獎金;\n獎金 -> EOS;\n金;\n金 -> EOS;\nEOS;\n}\n"
|
||||
XCTAssertEqual(compositor.dumpDOT, expectedDumpDOT)
|
||||
}
|
||||
|
||||
func testCompositor_InputTest2() throws {
|
||||
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData))
|
||||
compositor.separator = ""
|
||||
compositor.insertKey("gao1")
|
||||
compositor.insertKey("ke1")
|
||||
compositor.insertKey("ji4")
|
||||
var result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高科技"])
|
||||
compositor.insertKey("gong1")
|
||||
compositor.insertKey("si1")
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高科技", "公司"])
|
||||
}
|
||||
|
||||
func testCompositor_OverrideOverlappingNodes() throws {
|
||||
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData))
|
||||
compositor.separator = ""
|
||||
compositor.insertKey("gao1")
|
||||
compositor.insertKey("ke1")
|
||||
compositor.insertKey("ji4")
|
||||
var result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高科技"])
|
||||
compositor.cursor = 0
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("膏", at: compositor.cursor))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["膏", "科技"])
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("高科技", at: 1))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高科技"])
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("膏", at: 0))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["膏", "科技"])
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("柯", at: 1))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["膏", "柯", "際"])
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("暨", at: 2))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["膏", "柯", "暨"])
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("高科技", at: 3))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高科技"])
|
||||
}
|
||||
|
||||
func testCompositor_OverrideReset() throws {
|
||||
var compositor = Megrez.Compositor(
|
||||
with: SimpleLM(input: strSampleData + "zhong1jiang3 終講 -11.0\n" + "jiang3jin1 槳襟 -11.0\n"))
|
||||
compositor.separator = ""
|
||||
compositor.insertKey("nian2")
|
||||
compositor.insertKey("zhong1")
|
||||
compositor.insertKey("jiang3")
|
||||
compositor.insertKey("jin1")
|
||||
var result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["年中", "獎金"])
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("終講", at: 1))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["年", "終講", "金"])
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("槳襟", at: 2))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["年中", "槳襟"])
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidateLiteral("年終", at: 0))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["年終", "槳襟"])
|
||||
}
|
||||
|
||||
func testCompositor_CandidateDisambiguation() throws {
|
||||
var compositor = Megrez.Compositor(with: SimpleLM(input: strEmojiSampleData))
|
||||
compositor.separator = ""
|
||||
compositor.insertKey("gao1")
|
||||
compositor.insertKey("re4")
|
||||
compositor.insertKey("huo3")
|
||||
compositor.insertKey("yan4")
|
||||
compositor.insertKey("wei2")
|
||||
compositor.insertKey("xian3")
|
||||
var result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高熱", "火焰", "危險"])
|
||||
let location = 2
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidate(.init(key: "huo3", value: "🔥"), at: location))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高熱", "🔥", "焰", "危險"])
|
||||
|
||||
XCTAssertTrue(compositor.overrideCandidate(.init(key: "huo3yan4", value: "🔥"), at: location))
|
||||
result = compositor.walk().0
|
||||
XCTAssertEqual(result.values, ["高熱", "🔥", "危險"])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
.PHONY: format
|
||||
|
||||
format:
|
||||
swiftformat ./ --swiftversion 5.5
|
||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format format --in-place --configuration ./.clang-format-swift.json --parallel
|
||||
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "PinyinPhonaConverter",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "PinyinPhonaConverter",
|
||||
targets: ["PinyinPhonaConverter"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "PinyinPhonaConverter",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# PinyinPhonaConverter
|
||||
|
||||
拼音轉注音模組。因為太耽誤編譯時間,所以單獨拿出來寫了一個給 LangModelAssembly 專用的版本。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -0,0 +1,92 @@
|
|||
// (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 Cocoa
|
||||
|
||||
extension String {
|
||||
public mutating func converToPhonabets(newToneOne: String = "") {
|
||||
if isEmpty || contains("_") || !isNotPureAlphanumerical { return }
|
||||
for key in arrHanyuPinyinToPhonabets {
|
||||
self = replacingOccurrences(of: key.0, with: key.1)
|
||||
}
|
||||
self = replacingOccurrences(of: " ", with: newToneOne)
|
||||
}
|
||||
}
|
||||
|
||||
/// 檢測字串是否包含半形英數內容
|
||||
extension String {
|
||||
fileprivate var isNotPureAlphanumerical: Bool {
|
||||
let regex = ".*[^A-Za-z0-9].*"
|
||||
let testString = NSPredicate(format: "SELF MATCHES %@", regex)
|
||||
return testString.evaluate(with: self)
|
||||
}
|
||||
}
|
||||
|
||||
private let arrHanyuPinyinToPhonabets: [(String, String)] = [
|
||||
("chuang", "ㄔㄨㄤ"), ("shuang", "ㄕㄨㄤ"), ("zhuang", "ㄓㄨㄤ"), ("chang", "ㄔㄤ"), ("cheng", "ㄔㄥ"), ("chong", "ㄔㄨㄥ"),
|
||||
("chuai", "ㄔㄨㄞ"), ("chuan", "ㄔㄨㄢ"), ("guang", "ㄍㄨㄤ"), ("huang", "ㄏㄨㄤ"), ("jiang", "ㄐㄧㄤ"), ("jiong", "ㄐㄩㄥ"),
|
||||
("kiang", "ㄎㄧㄤ"), ("kuang", "ㄎㄨㄤ"), ("biang", "ㄅㄧㄤ"), ("duang", "ㄉㄨㄤ"), ("liang", "ㄌㄧㄤ"), ("niang", "ㄋㄧㄤ"),
|
||||
("qiang", "ㄑㄧㄤ"), ("qiong", "ㄑㄩㄥ"), ("shang", "ㄕㄤ"), ("sheng", "ㄕㄥ"), ("shuai", "ㄕㄨㄞ"), ("shuan", "ㄕㄨㄢ"),
|
||||
("xiang", "ㄒㄧㄤ"), ("xiong", "ㄒㄩㄥ"), ("zhang", "ㄓㄤ"), ("zheng", "ㄓㄥ"), ("zhong", "ㄓㄨㄥ"), ("zhuai", "ㄓㄨㄞ"),
|
||||
("zhuan", "ㄓㄨㄢ"), ("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", "ㄗㄨㄢ"), ("jun", "ㄐㄩㄣ"), ("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", "ㄍㄟ"), ("gin", "ㄍㄧㄣ"), ("gen", "ㄍㄣ"), ("gou", "ㄍㄡ"), ("gua", "ㄍㄨㄚ"),
|
||||
("gue", "ㄍㄨㄜ"), ("gui", "ㄍㄨㄟ"), ("gun", "ㄍㄨㄣ"), ("guo", "ㄍㄨㄛ"), ("hai", "ㄏㄞ"), ("han", "ㄏㄢ"), ("hao", "ㄏㄠ"),
|
||||
("hei", "ㄏㄟ"), ("hen", "ㄏㄣ"), ("hou", "ㄏㄡ"), ("hua", "ㄏㄨㄚ"), ("hui", "ㄏㄨㄟ"), ("hun", "ㄏㄨㄣ"), ("huo", "ㄏㄨㄛ"),
|
||||
("jia", "ㄐㄧㄚ"), ("jie", "ㄐㄧㄝ"), ("jin", "ㄐㄧㄣ"), ("jiu", "ㄐㄧㄡ"), ("jue", "ㄐㄩㄝ"), ("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", "ㄗㄨㄛ"), ("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", "ㄗㄨ"), ("a", "ㄚ"), ("e", "ㄜ"), ("o", "ㄛ"), ("q", "ㄑ"),
|
||||
("1", " "), ("2", "ˊ"), ("3", "ˇ"), ("4", "ˋ"), ("5", "˙"),
|
||||
]
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,26 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Shared",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Shared",
|
||||
targets: ["Shared"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../vChewing_CocoaExtension")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Shared",
|
||||
dependencies: [
|
||||
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# Protocols
|
||||
|
||||
威注音輸入法用到的各種協定與共用靜態內容、以及少許全局共用工具函式。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -0,0 +1,59 @@
|
|||
// (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 Cocoa
|
||||
import CocoaExtension
|
||||
|
||||
extension NSEvent: InputSignalProtocol {}
|
||||
|
||||
// MARK: - InputSignalProtocol
|
||||
|
||||
public protocol InputSignalProtocol {
|
||||
var isTypingVertical: Bool { get }
|
||||
var text: String { get }
|
||||
var inputTextIgnoringModifiers: String? { get }
|
||||
var charCode: UInt16 { get }
|
||||
var keyCode: UInt16 { get }
|
||||
var isFlagChanged: Bool { get }
|
||||
var mainAreaNumKeyChar: String? { get }
|
||||
var isASCII: Bool { get }
|
||||
var isInvalid: Bool { get }
|
||||
var isKeyCodeBlacklisted: Bool { get }
|
||||
var isReservedKey: Bool { get }
|
||||
var isNumericPadKey: Bool { get }
|
||||
var isMainAreaNumKey: Bool { get }
|
||||
var isShiftHold: Bool { get }
|
||||
var isCommandHold: Bool { get }
|
||||
var isControlHold: Bool { get }
|
||||
var isControlHotKey: Bool { get }
|
||||
var isOptionHold: Bool { get }
|
||||
var isOptionHotKey: Bool { get }
|
||||
var isCapsLockOn: Bool { get }
|
||||
var isFunctionKeyHold: Bool { get }
|
||||
var isNonLaptopFunctionKey: Bool { get }
|
||||
var isEnter: Bool { get }
|
||||
var isTab: Bool { get }
|
||||
var isUp: Bool { get }
|
||||
var isDown: Bool { get }
|
||||
var isLeft: Bool { get }
|
||||
var isRight: Bool { get }
|
||||
var isPageUp: Bool { get }
|
||||
var isPageDown: Bool { get }
|
||||
var isSpace: Bool { get }
|
||||
var isBackSpace: Bool { get }
|
||||
var isEsc: Bool { get }
|
||||
var isHome: Bool { get }
|
||||
var isEnd: Bool { get }
|
||||
var isDelete: Bool { get }
|
||||
var isCursorBackward: Bool { get }
|
||||
var isCursorForward: Bool { get }
|
||||
var isCursorClockRight: Bool { get }
|
||||
var isCursorClockLeft: Bool { get }
|
||||
var isUpperCaseASCIILetterKey: Bool { get }
|
||||
var isSymbolMenuPhysicalKey: Bool { get }
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// (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 Foundation
|
||||
|
||||
public protocol PrefMgrProtocol {
|
||||
var isDebugModeEnabled: Bool { get set }
|
||||
var failureFlagForUOMObservation: Bool { get set }
|
||||
var deltaOfCalendarYears: Int { get set }
|
||||
var mostRecentInputMode: String { get set }
|
||||
var checkUpdateAutomatically: Bool { get set }
|
||||
var userDataFolderSpecified: String { get set }
|
||||
var appleLanguages: [String] { get set }
|
||||
var keyboardParser: Int { get set }
|
||||
var basicKeyboardLayout: String { get set }
|
||||
var alphanumericalKeyboardLayout: String { get set }
|
||||
var showPageButtonsInCandidateWindow: Bool { get set }
|
||||
var candidateListTextSize: Double { get set }
|
||||
var shouldAutoReloadUserDataFiles: Bool { get set }
|
||||
var useRearCursorMode: Bool { get set }
|
||||
var moveCursorAfterSelectingCandidate: Bool { get set }
|
||||
var useHorizontalCandidateList: Bool { get set }
|
||||
var chooseCandidateUsingSpace: Bool { get set }
|
||||
var allowBoostingSingleKanjiAsUserPhrase: Bool { get set }
|
||||
var fetchSuggestionsFromUserOverrideModel: Bool { get set }
|
||||
var useFixecCandidateOrderOnSelection: Bool { get set }
|
||||
var autoCorrectReadingCombination: Bool { get set }
|
||||
var alsoConfirmAssociatedCandidatesByEnter: Bool { get set }
|
||||
var keepReadingUponCompositionError: Bool { get set }
|
||||
var upperCaseLetterKeyBehavior: Int { get set }
|
||||
var togglingAlphanumericalModeWithLShift: Bool { get set }
|
||||
var disableShiftTogglingAlphanumericalMode: Bool { get set }
|
||||
var consolidateContextOnCandidateSelection: Bool { get set }
|
||||
var hardenVerticalPunctuations: Bool { get set }
|
||||
var trimUnfinishedReadingsOnCommit: Bool { get set }
|
||||
var alwaysShowTooltipTextsHorizontally: Bool { get set }
|
||||
var clientsIMKTextInputIncapable: [String] { get set }
|
||||
var useIMKCandidateWindow: Bool { get set }
|
||||
var handleDefaultCandidateFontsByLangIdentifier: Bool { get set }
|
||||
var shiftKeyAccommodationBehavior: Int { get set }
|
||||
var maxCandidateLength: Int { get set }
|
||||
var shouldNotFartInLieuOfBeep: Bool { get set }
|
||||
var showHanyuPinyinInCompositionBuffer: Bool { get set }
|
||||
var inlineDumpPinyinInLieuOfZhuyin: Bool { get set }
|
||||
var cns11643Enabled: Bool { get set }
|
||||
var symbolInputEnabled: Bool { get set }
|
||||
var chineseConversionEnabled: Bool { get set }
|
||||
var shiftJISShinjitaiOutputEnabled: Bool { get set }
|
||||
var currencyNumeralsEnabled: Bool { get set }
|
||||
var halfWidthPunctuationEnabled: Bool { get set }
|
||||
var escToCleanInputBuffer: Bool { get set }
|
||||
var specifyIntonationKeyBehavior: Int { get set }
|
||||
var specifyShiftBackSpaceKeyBehavior: Int { get set }
|
||||
var specifyShiftTabKeyBehavior: Bool { get set }
|
||||
var specifyShiftSpaceKeyBehavior: Bool { get set }
|
||||
var candidateTextFontName: String { get set }
|
||||
var candidateKeyLabelFontName: String { get set }
|
||||
var candidateKeys: String { get set }
|
||||
var useSCPCTypingMode: Bool { get set }
|
||||
var phraseReplacementEnabled: Bool { get set }
|
||||
var associatedPhrasesEnabled: Bool { get set }
|
||||
var usingHotKeySCPC: Bool { get set }
|
||||
var usingHotKeyAssociates: Bool { get set }
|
||||
var usingHotKeyCNS: Bool { get set }
|
||||
var usingHotKeyKangXi: Bool { get set }
|
||||
var usingHotKeyJIS: Bool { get set }
|
||||
var usingHotKeyHalfWidthASCII: Bool { get set }
|
||||
var usingHotKeyCurrencyNumerals: Bool { get set }
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue