From 86aed008c3dbb355c26ba90fcd4a8036eba5da06 Mon Sep 17 00:00:00 2001 From: mtgto Date: Wed, 20 Nov 2024 23:28:14 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E5=8F=A5=E7=82=B9=E3=81=A8=E8=AA=AD?= =?UTF-8?q?=E7=82=B9=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=81=AE=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Settings/SettingsViewModel.swift | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/macSKK/Settings/SettingsViewModel.swift b/macSKK/Settings/SettingsViewModel.swift index f7e3e0b..9bd5297 100644 --- a/macSKK/Settings/SettingsViewModel.swift +++ b/macSKK/Settings/SettingsViewModel.swift @@ -189,6 +189,36 @@ enum SelectingBackspace: Int, CaseIterable, Identifiable { } } +/** + * ピリオドを入力したときに使用する句点の設定。 + * ローマ字かな変換ルールを上書きすることが可能。 + */ +enum Period: Int, CaseIterable, Identifiable { + typealias ID = Int + var id: ID { rawValue } + /// ローマ字かな変換ルールをそのまま適用する + case `default` = 0 + /// "。" を入力する + case maru = 1 + /// "." (全角ピリオド) を入力する + case period = 2 +} + +/** + * カンマを入力したときに使用する読点の設定。 + * ローマ字かな変換ルールを上書きすることが可能。 + */ +enum Comma: Int, CaseIterable, Identifiable { + typealias ID = Int + var id: ID { rawValue } + /// ローマ字かな変換ルールをそのまま適用する + case `default` = 0 + /// "、" を入力する + case ten = 1 + /// "," (全角カンマ) を入力する + case comma = 2 +} + @MainActor final class SettingsViewModel: ObservableObject { /// CheckUpdaterで取得した最新のリリース。取得前はnil From 97e2dd9ecdc6974c21e7b51c8264d8e980b3ad68 Mon Sep 17 00:00:00 2001 From: mtgto Date: Wed, 20 Nov 2024 23:37:48 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E5=8F=A5=E7=82=B9=E3=81=A8=E8=AA=AD?= =?UTF-8?q?=E7=82=B9=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=92SettingsViewModel?= =?UTF-8?q?=E3=81=AB=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Settings/SettingsViewModel.swift | 6 ++++++ macSKK/Settings/UserDefaultsKeys.swift | 4 ++++ macSKK/macSKKApp.swift | 2 ++ 3 files changed, 12 insertions(+) diff --git a/macSKK/Settings/SettingsViewModel.swift b/macSKK/Settings/SettingsViewModel.swift index 9bd5297..034ea4c 100644 --- a/macSKK/Settings/SettingsViewModel.swift +++ b/macSKK/Settings/SettingsViewModel.swift @@ -261,6 +261,8 @@ final class SettingsViewModel: ObservableObject { @Published var enterNewLine: Bool @Published var systemDict: SystemDict.Kind @Published var selectingBackspace: SelectingBackspace + @Published var period: Period + @Published var comma: Comma // 辞書ディレクトリ let dictionariesDirectoryUrl: URL @@ -314,6 +316,8 @@ final class SettingsViewModel: ObservableObject { selectCandidateKeys = UserDefaults.standard.string(forKey: UserDefaultsKeys.selectCandidateKeys)! enterNewLine = UserDefaults.standard.bool(forKey: UserDefaultsKeys.enterNewLine) selectingBackspace = SelectingBackspace(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.selectingBackspace)) ?? SelectingBackspace.default + comma = Comma(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.comma)) ?? .default + period = Period(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.period)) ?? .default Global.selectCandidateKeys = selectCandidateKeys.lowercased().map { $0 } Global.systemDict = systemDict Global.selectingBackspace = selectingBackspace @@ -530,6 +534,8 @@ final class SettingsViewModel: ObservableObject { enterNewLine = false systemDict = .daijirin selectingBackspace = SelectingBackspace.default + comma = Comma.default + period = Period.default } // DictionaryViewのPreviewProvider用 diff --git a/macSKK/Settings/UserDefaultsKeys.swift b/macSKK/Settings/UserDefaultsKeys.swift index 0751d1b..3cb1a38 100644 --- a/macSKK/Settings/UserDefaultsKeys.swift +++ b/macSKK/Settings/UserDefaultsKeys.swift @@ -33,4 +33,8 @@ struct UserDefaultsKeys { static let systemDict = "systemDict" // 変換候補選択中のバックスペースの挙動 static let selectingBackspace = "selectingBackspace" + // カンマ入力時の読点 + static let comma = "comma" + // ピリオド入力時の句点 + static let period = "period" } diff --git a/macSKK/macSKKApp.swift b/macSKK/macSKKApp.swift index ce295c0..cc7c148 100644 --- a/macSKK/macSKKApp.swift +++ b/macSKK/macSKKApp.swift @@ -195,6 +195,8 @@ struct macSKKApp: App { UserDefaultsKeys.enterNewLine: false, UserDefaultsKeys.systemDict: SystemDict.Kind.daijirin.rawValue, UserDefaultsKeys.selectingBackspace: SelectingBackspace.default.rawValue, + UserDefaultsKeys.comma: Comma.default.rawValue, + UserDefaultsKeys.period: Period.default.rawValue, ]) } From 23c893910713f886505b5caa8cb5ca0472157105 Mon Sep 17 00:00:00 2001 From: mtgto Date: Thu, 21 Nov 2024 23:22:19 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E5=8F=A5=E8=AA=AD=E7=82=B9=E3=81=AE?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=AFUserDefaults=E3=81=AB=E3=81=AF1?= =?UTF-8?q?=E3=82=AD=E3=83=BC=E3=81=A7=E4=BF=9D=E5=AD=98=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Global.swift | 4 ++ macSKK/Settings/SettingsViewModel.swift | 56 ++++++++++++++++++------- macSKK/Settings/UserDefaultsKeys.swift | 6 +-- macSKK/macSKKApp.swift | 3 +- 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/macSKK/Global.swift b/macSKK/Global.swift index cca7141..effb031 100644 --- a/macSKK/Global.swift +++ b/macSKK/Global.swift @@ -37,6 +37,10 @@ import Combine static var systemDict: SystemDict.Kind = .daijirin /// 変換候補選択中のバックスペースの挙動 static var selectingBackspace: SelectingBackspace = .default + /// カンマを入力したときに使用する読点の設定 + static var comma: Comma = .default + /// ピリオドを入力したときに使用する読点の設定 + static var period: Period = .default /// 現在のモードを表示するパネル private let inputModePanel: InputModePanel /// 変換候補を表示するパネル diff --git a/macSKK/Settings/SettingsViewModel.swift b/macSKK/Settings/SettingsViewModel.swift index 034ea4c..96da069 100644 --- a/macSKK/Settings/SettingsViewModel.swift +++ b/macSKK/Settings/SettingsViewModel.swift @@ -190,33 +190,53 @@ enum SelectingBackspace: Int, CaseIterable, Identifiable { } /** - * ピリオドを入力したときに使用する句点の設定。 + * カンマを入力したときに使用する読点の設定。 * ローマ字かな変換ルールを上書きすることが可能。 */ -enum Period: Int, CaseIterable, Identifiable { +enum Comma: Int, CaseIterable, Identifiable { typealias ID = Int var id: ID { rawValue } /// ローマ字かな変換ルールをそのまま適用する case `default` = 0 - /// "。" を入力する - case maru = 1 - /// "." (全角ピリオド) を入力する - case period = 2 + /// "、" を入力する + case ten = 1 + /// "," (全角カンマ) を入力する + case comma = 2 + + init?(rawValue: Int) { + switch rawValue & 3 { + case 0: self = .default + case 1: self = .ten + case 2: self = .comma + default: + return nil + } + } } /** - * カンマを入力したときに使用する読点の設定。 + * ピリオドを入力したときに使用する句点の設定。 * ローマ字かな変換ルールを上書きすることが可能。 */ -enum Comma: Int, CaseIterable, Identifiable { +enum Period: Int, CaseIterable, Identifiable { typealias ID = Int var id: ID { rawValue } /// ローマ字かな変換ルールをそのまま適用する case `default` = 0 - /// "、" を入力する - case ten = 1 - /// "," (全角カンマ) を入力する - case comma = 2 + /// "。" を入力する + case maru = 4 + /// "." (全角ピリオド) を入力する + case period = 8 + + init?(rawValue: Int) { + switch rawValue & 12 { + case 0: self = .default + case 4: self = .maru + case 8: self = .period + default: + return nil + } + } } @MainActor @@ -316,11 +336,13 @@ final class SettingsViewModel: ObservableObject { selectCandidateKeys = UserDefaults.standard.string(forKey: UserDefaultsKeys.selectCandidateKeys)! enterNewLine = UserDefaults.standard.bool(forKey: UserDefaultsKeys.enterNewLine) selectingBackspace = SelectingBackspace(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.selectingBackspace)) ?? SelectingBackspace.default - comma = Comma(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.comma)) ?? .default - period = Period(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.period)) ?? .default + comma = Comma(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.punctuation)) ?? .default + period = Period(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.punctuation)) ?? .default Global.selectCandidateKeys = selectCandidateKeys.lowercased().map { $0 } Global.systemDict = systemDict Global.selectingBackspace = selectingBackspace + Global.comma = comma + Global.period = period // SKK-JISYO.Lのようなファイルの読み込みが遅いのでバックグラウンドで処理 $dictSettings.filter({ !$0.isEmpty }).receive(on: DispatchQueue.global()).sink { dictSettings in @@ -383,6 +405,12 @@ final class SettingsViewModel: ObservableObject { Global.insertBlankStringBundleIdentifiers.send(applications.filter { $0.insertBlankString }.map { $0.bundleIdentifier }) }.store(in: &cancellables) + $comma.combineLatest($period).dropFirst().sink { (comma, period) in + Global.comma = comma + Global.period = period + UserDefaults.standard.set(comma.rawValue | period.rawValue, forKey: UserDefaultsKeys.punctuation) + }.store(in: &cancellables) + NotificationCenter.default.publisher(for: notificationNameToggleDirectMode) .sink { [weak self] notification in if let bundleIdentifier = notification.object as? String { diff --git a/macSKK/Settings/UserDefaultsKeys.swift b/macSKK/Settings/UserDefaultsKeys.swift index 3cb1a38..6437c34 100644 --- a/macSKK/Settings/UserDefaultsKeys.swift +++ b/macSKK/Settings/UserDefaultsKeys.swift @@ -33,8 +33,6 @@ struct UserDefaultsKeys { static let systemDict = "systemDict" // 変換候補選択中のバックスペースの挙動 static let selectingBackspace = "selectingBackspace" - // カンマ入力時の読点 - static let comma = "comma" - // ピリオド入力時の句点 - static let period = "period" + // カンマ、ピリオド入力時の句読点 + static let punctuation = "punctuation" } diff --git a/macSKK/macSKKApp.swift b/macSKK/macSKKApp.swift index cc7c148..04e3145 100644 --- a/macSKK/macSKKApp.swift +++ b/macSKK/macSKKApp.swift @@ -195,8 +195,7 @@ struct macSKKApp: App { UserDefaultsKeys.enterNewLine: false, UserDefaultsKeys.systemDict: SystemDict.Kind.daijirin.rawValue, UserDefaultsKeys.selectingBackspace: SelectingBackspace.default.rawValue, - UserDefaultsKeys.comma: Comma.default.rawValue, - UserDefaultsKeys.period: Period.default.rawValue, + UserDefaultsKeys.punctuation: Comma.default.rawValue | Period.default.rawValue, ]) } From 343103df34baf4c9461d5f1cb2943a62b81a8296 Mon Sep 17 00:00:00 2001 From: mtgto Date: Sat, 23 Nov 2024 18:14:13 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Romaji#convert=E3=81=AE=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=81=AB=E5=8F=A5=E8=AA=AD=E7=82=B9=E3=81=AE=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Romaji.swift | 24 ++++++++++++++--- macSKK/StateMachine.swift | 12 ++++----- macSKKTests/RomajiTests.swift | 50 ++++++++++++++++++++--------------- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/macSKK/Romaji.swift b/macSKK/Romaji.swift index 5afe470..85a2c2b 100644 --- a/macSKK/Romaji.swift +++ b/macSKK/Romaji.swift @@ -269,18 +269,34 @@ struct Romaji: Equatable, Sendable { * - "kt" のように連続できない子音が連続したinputの場合は"k"を捨てて"t"をinput引数としたときのconvertの結果を返す * - "kya" のように確定した文字が複数の場合がありえる * - "aiueo" のように複数の確定が可能な場合は最初に確定できた文字だけを確定文字として返し、残りは(確定可能だが)inputとして返す - * - ",", "." は"、", "。"にする (将来設定で切り変えられるようにするかも) + * - ",", "." は引数 `comma`, `period` に従って変換する * - "1" のように非ローマ字文字列を受け取った場合は未定義とする (呼び出し側で処理する予定だけどここで処理するかも) */ - func convert(_ input: String) -> ConvertedMoji { - if let moji = table[input] { + func convert(_ input: String, comma: Comma, period: Period) -> ConvertedMoji { + if input == "," && comma != .default { + if case .ten = comma { + return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: "、")) + } else if case .comma = comma { + return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: ",")) + } else { + fatalError() + } + } else if input == "." && period != .default { + if case .maru = period { + return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "。")) + } else if case .period = period { + return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: ".")) + } else { + fatalError() + } + } else if let moji = table[input] { return ConvertedMoji(input: moji.remain ?? "", kakutei: moji) } else if undecidedInputs.contains(input) { return ConvertedMoji(input: input, kakutei: nil) } else if input.hasPrefix("n") && input.count == 2 { return ConvertedMoji(input: String(input.dropFirst()), kakutei: Romaji.n) } else if input.count > 1, let c = input.last { - return convert(String(c)) + return convert(String(c), comma: comma, period: period) } return ConvertedMoji(input: input, kakutei: nil) } diff --git a/macSKK/StateMachine.swift b/macSKK/StateMachine.swift index 09a9954..23940f0 100644 --- a/macSKK/StateMachine.swift +++ b/macSKK/StateMachine.swift @@ -364,7 +364,7 @@ final class StateMachine { switch state.inputMode { case .hiragana, .katakana, .hankaku: if input.isAlphabet && !action.optionIsPressed() { - let result = Global.kanaRule.convert(input) + let result = Global.kanaRule.convert(input, comma: Global.comma, period: Global.period) if let moji = result.kakutei { if action.shiftIsPressed() { state.inputMethod = .composing( @@ -389,7 +389,7 @@ final class StateMachine { cursorPosition: action.cursorPosition), specialState: specialState) } - let result = Global.kanaRule.convert(characters) + let result = Global.kanaRule.convert(characters, comma: Global.comma, period: Global.period) if let moji = result.kakutei { if action.shiftIsPressed() && moji.kana.isHiragana { state.inputMethod = .composing( @@ -631,7 +631,7 @@ final class StateMachine { if state.inputMode == .direct { return handleComposingPrintable( input: ";", - converted: Global.kanaRule.convert(";"), + converted: Global.kanaRule.convert(";", comma: Global.comma, period: Global.period), action: action, composing: composing, specialState: specialState) @@ -759,9 +759,9 @@ final class StateMachine { if let input { let converted: Romaji.ConvertedMoji if !input.isAlphabet, let characters = action.characters() { - converted = Global.kanaRule.convert(romaji + characters) + converted = Global.kanaRule.convert(romaji + characters, comma: Global.comma, period: Global.period) } else { - converted = Global.kanaRule.convert(romaji + input) + converted = Global.kanaRule.convert(romaji + input, comma: Global.comma, period: Global.period) } return handleComposingPrintable( @@ -915,7 +915,7 @@ final class StateMachine { @MainActor private func useKanaRuleIfPresent(inputMode: InputMode, romaji: String, input: String) -> Romaji.ConvertedMoji? { if inputMode != .direct && !romaji.isEmpty { - let converted = Global.kanaRule.convert(romaji + input) + let converted = Global.kanaRule.convert(romaji + input, comma: Global.comma, period: Global.period) if converted.kakutei != nil && converted.input == "" { return converted } else { diff --git a/macSKKTests/RomajiTests.swift b/macSKKTests/RomajiTests.swift index 86276c8..acb78da 100644 --- a/macSKKTests/RomajiTests.swift +++ b/macSKKTests/RomajiTests.swift @@ -18,26 +18,32 @@ class RomajiTests: XCTestCase { func testConvert() throws { let fileURL = Bundle(for: Self.self).url(forResource: "kana-rule-for-test", withExtension: "conf")! let kanaRule = try Romaji(contentsOf: fileURL) - XCTAssertEqual(kanaRule.convert("a"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana: "あ"))) + XCTAssertEqual(kanaRule.convert("a", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana: "あ"))) // nだけではまだ「ん」になるかは確定しない (な行などに派生する可能性がある) - XCTAssertEqual(kanaRule.convert("n"), Romaji.ConvertedMoji(input: "n", kakutei: nil)) - XCTAssertEqual(kanaRule.convert("na"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "な"))) - XCTAssertEqual(kanaRule.convert("ga"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "g", kana: "が"))) - XCTAssertEqual(kanaRule.convert("ji"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ"))) - XCTAssertEqual(kanaRule.convert("zi"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ")), "かなが「じ」のときはfirstRomajiはj固定") - XCTAssertEqual(kanaRule.convert("nn"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) - XCTAssertEqual(kanaRule.convert("nk"), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) - XCTAssertEqual(kanaRule.convert("n!"), Romaji.ConvertedMoji(input: "!", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) - XCTAssertEqual(kanaRule.convert("kn"), Romaji.ConvertedMoji(input: "n", kakutei: nil)) - XCTAssertEqual(kanaRule.convert("ny"), Romaji.ConvertedMoji(input: "ny", kakutei: nil)) - XCTAssertEqual(kanaRule.convert("nya"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "にゃ"))) - XCTAssertEqual(kanaRule.convert("kk"), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "k", kana: "っ", katakana: "ッ", hankaku: "ッ", remain: "k"))) - XCTAssertEqual(kanaRule.convert("nyk"), Romaji.ConvertedMoji(input: "k", kakutei: nil), "続けられない子音が連続した場合は最後の子音だけ残る") - XCTAssertEqual(kanaRule.convert("z,"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "z", kana: "‥"))) - XCTAssertEqual(kanaRule.convert("x,,"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "d", kana: "だぶるかんま")), "firstRomajiはかなの一文字目から決定される") - XCTAssertEqual(kanaRule.convert("@"), Romaji.ConvertedMoji(input: "@", kakutei: nil), "ルールにない文字は変換されない") - XCTAssertEqual(kanaRule.convert("a;"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana:"あせみころん")), "システム用の文字を含むことができる") - XCTAssertEqual(kanaRule.convert("ca"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "k", kana:"か")), "実際に入力した一文字目(c)ではなく「か」からローマ字(k)に変換する") + XCTAssertEqual(kanaRule.convert("n", comma: .default, period: .default), Romaji.ConvertedMoji(input: "n", kakutei: nil)) + XCTAssertEqual(kanaRule.convert("na", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "な"))) + XCTAssertEqual(kanaRule.convert("ga", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "g", kana: "が"))) + XCTAssertEqual(kanaRule.convert("ji", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ"))) + XCTAssertEqual(kanaRule.convert("zi", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ")), "かなが「じ」のときはfirstRomajiはj固定") + XCTAssertEqual(kanaRule.convert("nn", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) + XCTAssertEqual(kanaRule.convert("nk", comma: .default, period: .default), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) + XCTAssertEqual(kanaRule.convert("n!", comma: .default, period: .default), Romaji.ConvertedMoji(input: "!", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) + XCTAssertEqual(kanaRule.convert("kn", comma: .default, period: .default), Romaji.ConvertedMoji(input: "n", kakutei: nil)) + XCTAssertEqual(kanaRule.convert("ny", comma: .default, period: .default), Romaji.ConvertedMoji(input: "ny", kakutei: nil)) + XCTAssertEqual(kanaRule.convert("nya", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "にゃ"))) + XCTAssertEqual(kanaRule.convert("kk", comma: .default, period: .default), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "k", kana: "っ", katakana: "ッ", hankaku: "ッ", remain: "k"))) + XCTAssertEqual(kanaRule.convert("nyk", comma: .default, period: .default), Romaji.ConvertedMoji(input: "k", kakutei: nil), "続けられない子音が連続した場合は最後の子音だけ残る") + XCTAssertEqual(kanaRule.convert("z,", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "z", kana: "‥"))) + XCTAssertEqual(kanaRule.convert("x,,", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "d", kana: "だぶるかんま")), "firstRomajiはかなの一文字目から決定される") + XCTAssertEqual(kanaRule.convert("@", comma: .default, period: .default), Romaji.ConvertedMoji(input: "@", kakutei: nil), "ルールにない文字は変換されない") + XCTAssertEqual(kanaRule.convert("a;", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana:"あせみころん")), "システム用の文字を含むことができる") + XCTAssertEqual(kanaRule.convert("ca", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "k", kana:"か")), "実際に入力した一文字目(c)ではなく「か」からローマ字(k)に変換する") + // 読点 + XCTAssertEqual(kanaRule.convert(",", comma: .ten, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: "、"))) + XCTAssertEqual(kanaRule.convert(",", comma: .comma, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: ","))) + // 句点 + XCTAssertEqual(kanaRule.convert(".", comma: .default, period: .maru), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "。"))) + XCTAssertEqual(kanaRule.convert(".", comma: .comma, period: .period), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "."))) XCTAssertEqual(kanaRule.lowercaseMap["+"], ";") XCTAssertEqual(kanaRule.lowercaseMap[":"], ";") } @@ -46,13 +52,13 @@ class RomajiTests: XCTestCase { // AZIKのセミコロンを促音(っ)として扱う設定 let kanaRule = try Romaji(source: ";,っ") // 入力はセミコロンでもfirstRomajiは "t" になる - XCTAssertEqual(kanaRule.convert(";"), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "t", kana: "っ"))) + XCTAssertEqual(kanaRule.convert(";", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "t", kana: "っ"))) } func testVu() throws { var kanaRule = try Romaji(source: "vu,う゛") - XCTAssertEqual(kanaRule.convert("vu").kakutei?.kana, "う゛") + XCTAssertEqual(kanaRule.convert("vu", comma: .default, period: .default).kakutei?.kana, "う゛") kanaRule = try Romaji(source: "vu,ゔ") - XCTAssertEqual(kanaRule.convert("vu").kakutei?.kana, "ゔ") + XCTAssertEqual(kanaRule.convert("vu", comma: .default, period: .default).kakutei?.kana, "ゔ") } } From 354a8fda6e95f09e942ccc1e7eba29187dd67561 Mon Sep 17 00:00:00 2001 From: mtgto Date: Sat, 23 Nov 2024 18:54:10 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E5=8F=A5=E8=AA=AD=E7=82=B9=E3=81=AE?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=82=92GUI=E3=81=AB=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Settings/GeneralView.swift | 10 +++++++ macSKK/Settings/SettingsViewModel.swift | 35 ++++++++++++++++++++----- macSKK/en.lproj/Localizable.strings | 4 +++ macSKK/ja.lproj/Localizable.strings | 4 +++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/macSKK/Settings/GeneralView.swift b/macSKK/Settings/GeneralView.swift index da554e3..7dc2c11 100644 --- a/macSKK/Settings/GeneralView.swift +++ b/macSKK/Settings/GeneralView.swift @@ -29,6 +29,16 @@ struct GeneralView: View { Text("ASDFGHJKL").tag("ASDFGHJKL") Text("AOEUIDHTN").tag("AOEUIDHTN") } + Picker("Behavior of Comma", selection: $settingsViewModel.comma) { + ForEach(Comma.allCases, id: \.id) { comma in + Text(comma.description).tag(comma) + } + } + Picker("Behavior of Period", selection: $settingsViewModel.period) { + ForEach(Period.allCases, id: \.id) { period in + Text(period.description).tag(period) + } + } Section { Picker("Number of inline candidates", selection: $settingsViewModel.inlineCandidateCount) { ForEach(0..<10) { count in diff --git a/macSKK/Settings/SettingsViewModel.swift b/macSKK/Settings/SettingsViewModel.swift index 96da069..cc17184 100644 --- a/macSKK/Settings/SettingsViewModel.swift +++ b/macSKK/Settings/SettingsViewModel.swift @@ -212,6 +212,17 @@ enum Comma: Int, CaseIterable, Identifiable { return nil } } + + var description: String { + switch self { + case .default: + return String(localized: "Follow Romaji-Kana Rule") + case .ten: + return String(format: String(localized: "EnterKey"), "、") + case .comma: + return String(format: String(localized: "EnterKey"), ",") + } + } } /** @@ -237,6 +248,17 @@ enum Period: Int, CaseIterable, Identifiable { return nil } } + + var description: String { + switch self { + case .default: + return String(localized: "Follow Romaji-Kana Rule") + case .maru: + return String(format: String(localized: "EnterKey"), "。") + case .period: + return String(format: String(localized: "EnterKey"), ".") + } + } } @MainActor @@ -405,12 +427,6 @@ final class SettingsViewModel: ObservableObject { Global.insertBlankStringBundleIdentifiers.send(applications.filter { $0.insertBlankString }.map { $0.bundleIdentifier }) }.store(in: &cancellables) - $comma.combineLatest($period).dropFirst().sink { (comma, period) in - Global.comma = comma - Global.period = period - UserDefaults.standard.set(comma.rawValue | period.rawValue, forKey: UserDefaultsKeys.punctuation) - }.store(in: &cancellables) - NotificationCenter.default.publisher(for: notificationNameToggleDirectMode) .sink { [weak self] notification in if let bundleIdentifier = notification.object as? String { @@ -523,6 +539,13 @@ final class SettingsViewModel: ObservableObject { Global.selectingBackspace = selectingBackspace }.store(in: &cancellables) + $comma.combineLatest($period).dropFirst().sink { (comma, period) in + logger.log("句読点の入力が変更されました。 カンマ: \(comma.description, privacy: .public), ピリオド: \(period.description, privacy: .public)") + Global.comma = comma + Global.period = period + UserDefaults.standard.set(comma.rawValue | period.rawValue, forKey: UserDefaultsKeys.punctuation) + }.store(in: &cancellables) + NotificationCenter.default.publisher(for: notificationNameDictLoad).receive(on: RunLoop.main).sink { [weak self] notification in if let loadEvent = notification.object as? DictLoadEvent, let self { if let userDict = Global.dictionary.userDict as? FileDict, userDict.id == loadEvent.id { diff --git a/macSKK/en.lproj/Localizable.strings b/macSKK/en.lproj/Localizable.strings index 5b95892..2eae5d8 100644 --- a/macSKK/en.lproj/Localizable.strings +++ b/macSKK/en.lproj/Localizable.strings @@ -65,6 +65,8 @@ "Copy" = "Copy"; "Number of inline candidates" = "Number of inline candidates"; "Backspace in selecting candidates" = "Backspace in selecting candidates"; +"Behavior of Comma" = "Behavior of Comma"; +"Behavior of Period" = "Behavior of Period"; "Candidates font size" = "Candidates font size"; "Annotation font size" = "Annotation font size"; "Find completion from all dictionaries" = "Find completion from all dictionaries"; @@ -122,3 +124,5 @@ "SelectingBackspaceCancel" = "Go back one step"; "SelectingBackspaceDropLastInlineOnly" = "Delete a last character & commit in Inline"; "SelectingBackspaceDropLastAlways" = "Delete a last character & commit always"; +"Follow Romaji-Kana Rule" = "Follow Romaji-Kana Rule"; +"EnterKey" = "Enter \"%@\""; diff --git a/macSKK/ja.lproj/Localizable.strings b/macSKK/ja.lproj/Localizable.strings index a54d64a..dc7f644 100644 --- a/macSKK/ja.lproj/Localizable.strings +++ b/macSKK/ja.lproj/Localizable.strings @@ -65,6 +65,8 @@ "Copy" = "コピー"; "Number of inline candidates" = "インラインで表示する変換候補の数"; "Backspace in selecting candidates" = "変換候補選択中のバックスペース"; +"Behavior of Comma" = "カンマの挙動"; +"Behavior of Period" = "ピリオドの挙動"; "Candidates font size" = "変換候補のフォントサイズ"; "Annotation font size" = "注釈のフォントサイズ"; "Find completion from all dictionaries" = "ユーザー辞書だけでなくすべての辞書から補完を探す"; @@ -122,3 +124,5 @@ "SelectingBackspaceCancel" = "一つ前の状態に戻す"; "SelectingBackspaceDropLastInlineOnly" = "インライン時は一文字削除して確定"; "SelectingBackspaceDropLastAlways" = "常に一文字削除して確定"; +"Follow Romaji-Kana Rule" = "ローマ字かな変換ルールに従う"; +"EnterKey" = "\"%@\" を入力"; From d1048dcb0ec549bdfcaba487a8fcd6692e165738 Mon Sep 17 00:00:00 2001 From: mtgto Date: Sun, 24 Nov 2024 21:23:32 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E5=8F=A5=E7=82=B9=E3=81=A8=E8=AA=AD?= =?UTF-8?q?=E7=82=B9=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=92=E3=82=82=E3=81=A4?= =?UTF-8?q?Punctuation=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK.xcodeproj/project.pbxproj | 8 ++ macSKK/Global.swift | 6 +- macSKK/Punctuation.swift | 101 ++++++++++++++++++++++++ macSKK/Romaji.swift | 16 ++-- macSKK/Settings/GeneralView.swift | 4 +- macSKK/Settings/SettingsViewModel.swift | 93 +++------------------- macSKK/StateMachine.swift | 12 +-- macSKK/macSKKApp.swift | 2 +- macSKKTests/PunctuationTests.swift | 20 +++++ macSKKTests/RomajiTests.swift | 52 ++++++------ 10 files changed, 184 insertions(+), 130 deletions(-) create mode 100644 macSKK/Punctuation.swift create mode 100644 macSKKTests/PunctuationTests.swift diff --git a/macSKK.xcodeproj/project.pbxproj b/macSKK.xcodeproj/project.pbxproj index b8ddfc3..93da9e3 100644 --- a/macSKK.xcodeproj/project.pbxproj +++ b/macSKK.xcodeproj/project.pbxproj @@ -89,6 +89,8 @@ CEA78FAE2961BA1D00B67E25 /* String+TransformTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA78FAD2961BA1D00B67E25 /* String+TransformTests.swift */; }; CEA78FB02964209B00B67E25 /* UserDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA78FAF2964209B00B67E25 /* UserDict.swift */; }; CEA78FB229646CA100B67E25 /* UserDictTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA78FB129646CA100B67E25 /* UserDictTests.swift */; }; + CEAC95572CF34E7700CD6D55 /* Punctuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAC95562CF34E7700CD6D55 /* Punctuation.swift */; }; + CEAC95592CF34F4100CD6D55 /* PunctuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAC95582CF34F4100CD6D55 /* PunctuationTests.swift */; }; CEADA44B2B025A0F0026E2BD /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEADA44A2B025A0F0026E2BD /* Entry.swift */; }; CEADA44D2B025A8A0026E2BD /* EntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEADA44C2B025A8A0026E2BD /* EntryTests.swift */; }; CEADA44F2B1357090026E2BD /* GeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEADA44E2B1357090026E2BD /* GeneralView.swift */; }; @@ -255,6 +257,8 @@ CEA78FAD2961BA1D00B67E25 /* String+TransformTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+TransformTests.swift"; sourceTree = ""; }; CEA78FAF2964209B00B67E25 /* UserDict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDict.swift; sourceTree = ""; }; CEA78FB129646CA100B67E25 /* UserDictTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDictTests.swift; sourceTree = ""; }; + CEAC95562CF34E7700CD6D55 /* Punctuation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Punctuation.swift; sourceTree = ""; }; + CEAC95582CF34F4100CD6D55 /* PunctuationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PunctuationTests.swift; sourceTree = ""; }; CEADA44A2B025A0F0026E2BD /* Entry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = ""; }; CEADA44C2B025A8A0026E2BD /* EntryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryTests.swift; sourceTree = ""; }; CEADA44E2B1357090026E2BD /* GeneralView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralView.swift; sourceTree = ""; }; @@ -414,6 +418,7 @@ CEADA44A2B025A0F0026E2BD /* Entry.swift */, CE4CB5CB2AD557D90046FA34 /* NumberEntry.swift */, CE84A3E6295DA4DA009394C4 /* Romaji.swift */, + CEAC95562CF34E7700CD6D55 /* Punctuation.swift */, 4B8E87D32CE05C3F004E7461 /* CurrentInput.swift */, 4B8E87D52CE05D0A004E7461 /* Key.swift */, CE11C7BE2BE47D9800A35F3D /* KeyBinding.swift */, @@ -477,6 +482,7 @@ CED1CA1D2BAFBE0600C32AE3 /* SKKServDictTests.swift */, CEADA44C2B025A8A0026E2BD /* EntryTests.swift */, CE84A3E8295DA504009394C4 /* RomajiTests.swift */, + CEAC95582CF34F4100CD6D55 /* PunctuationTests.swift */, 4B8E87D72CE0689D004E7461 /* CurrentInputTests.swift */, CE118EE42CE0B4DB00A7C300 /* KeyTests.swift */, CE2F3B122C030C9A00CE342B /* KeyBindingTests.swift */, @@ -817,6 +823,7 @@ CED987412BB953E7001B40F9 /* Data+EucJis2004.swift in Sources */, CE485A882A8FA195008271EF /* Release+UNNotification.swift in Sources */, CED7CA3A2A839505004EF988 /* FetchUpdateServiceProtocol.swift in Sources */, + CEAC95572CF34E7700CD6D55 /* Punctuation.swift in Sources */, CEA78FB02964209B00B67E25 /* UserDict.swift in Sources */, CEF08257296D8CBD00646366 /* CandidatesPanel.swift in Sources */, CEADA44B2B025A0F0026E2BD /* Entry.swift in Sources */, @@ -863,6 +870,7 @@ CE496C952B440BBD001C623C /* Data+EucJis2004Tests.swift in Sources */, CEE2D9792A99FEC700A4CD76 /* CandidateTest.swift in Sources */, CEF0823629685C0800646366 /* StateTests.swift in Sources */, + CEAC95592CF34F4100CD6D55 /* PunctuationTests.swift in Sources */, CED7CA3E2A8397E4004EF988 /* UpdateCheckerTests.swift in Sources */, CE8DFE092CBEAA2900A24230 /* Character+AdditionsTests.swift in Sources */, CE2F3B132C030C9A00CE342B /* KeyBindingTests.swift in Sources */, diff --git a/macSKK/Global.swift b/macSKK/Global.swift index effb031..4d947ef 100644 --- a/macSKK/Global.swift +++ b/macSKK/Global.swift @@ -37,10 +37,8 @@ import Combine static var systemDict: SystemDict.Kind = .daijirin /// 変換候補選択中のバックスペースの挙動 static var selectingBackspace: SelectingBackspace = .default - /// カンマを入力したときに使用する読点の設定 - static var comma: Comma = .default - /// ピリオドを入力したときに使用する読点の設定 - static var period: Period = .default + /// カンマかピリオドを入力したときに入力する句読点の設定 + static var punctuation: Punctuation = .default /// 現在のモードを表示するパネル private let inputModePanel: InputModePanel /// 変換候補を表示するパネル diff --git a/macSKK/Punctuation.swift b/macSKK/Punctuation.swift new file mode 100644 index 0000000..50e5051 --- /dev/null +++ b/macSKK/Punctuation.swift @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +/** + * カンマとピリオドを入力したときに入力される句読点の設定. + * ローマ字かな変換ルールを上書きすることが可能。 + */ +struct Punctuation { + enum Comma: Int, CaseIterable, Identifiable { + typealias ID = Int + var id: ID { rawValue } + /// ローマ字かな変換ルールをそのまま適用する + case `default` = 0 + /// "、" を入力する + case ten = 1 + /// "," (全角カンマ) を入力する + case comma = 2 + + init?(rawValue: Int) { + switch rawValue & 3 { + case 0: + self = .default + case 1: + self = .ten + case 2: + self = .comma + default: + return nil + } + } + + var description: String { + switch self { + case .default: + return String(localized: "Follow Romaji-Kana Rule") + case .ten: + return String(format: String(localized: "EnterKey"), "、") + case .comma: + return String(format: String(localized: "EnterKey"), ",") + } + } + } + + enum Period: Int, CaseIterable, Identifiable { + typealias ID = Int + var id: ID { rawValue } + /// ローマ字かな変換ルールをそのまま適用する + case `default` = 0 + /// "。" を入力する + case maru = 256 + /// "." (全角ピリオド) を入力する + case period = 512 + + init?(rawValue: Int) { + switch rawValue & 768 { + case 0: + self = .default + case 256: + self = .maru + case 512: + self = .period + default: + return nil + } + } + + var description: String { + switch self { + case .default: + return String(localized: "Follow Romaji-Kana Rule") + case .maru: + return String(format: String(localized: "EnterKey"), "。") + case .period: + return String(format: String(localized: "EnterKey"), ".") + } + } + } + + let comma: Comma + let period: Period + + static let `default`: Self = .init(comma: .default, period: .default) + + init(comma: Punctuation.Comma, period: Punctuation.Period) { + self.comma = comma + self.period = period + } + + init?(rawValue: Int) { + guard let comma = Comma(rawValue: rawValue), let period = Period(rawValue: rawValue) else { + return nil + } + self.comma = comma + self.period = period + } + + var rawValue: Int { + comma.rawValue | period.rawValue + } +} diff --git a/macSKK/Romaji.swift b/macSKK/Romaji.swift index 85a2c2b..d6c2ed6 100644 --- a/macSKK/Romaji.swift +++ b/macSKK/Romaji.swift @@ -272,19 +272,19 @@ struct Romaji: Equatable, Sendable { * - ",", "." は引数 `comma`, `period` に従って変換する * - "1" のように非ローマ字文字列を受け取った場合は未定義とする (呼び出し側で処理する予定だけどここで処理するかも) */ - func convert(_ input: String, comma: Comma, period: Period) -> ConvertedMoji { - if input == "," && comma != .default { - if case .ten = comma { + func convert(_ input: String, punctuation: Punctuation) -> ConvertedMoji { + if input == "," && punctuation.comma != .default { + if case .ten = punctuation.comma { return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: "、")) - } else if case .comma = comma { + } else if case .comma = punctuation.comma { return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: ",")) } else { fatalError() } - } else if input == "." && period != .default { - if case .maru = period { + } else if input == "." && punctuation.period != .default { + if case .maru = punctuation.period { return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "。")) - } else if case .period = period { + } else if case .period = punctuation.period { return ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: ".")) } else { fatalError() @@ -296,7 +296,7 @@ struct Romaji: Equatable, Sendable { } else if input.hasPrefix("n") && input.count == 2 { return ConvertedMoji(input: String(input.dropFirst()), kakutei: Romaji.n) } else if input.count > 1, let c = input.last { - return convert(String(c), comma: comma, period: period) + return convert(String(c), punctuation: punctuation) } return ConvertedMoji(input: input, kakutei: nil) } diff --git a/macSKK/Settings/GeneralView.swift b/macSKK/Settings/GeneralView.swift index 7dc2c11..45511b0 100644 --- a/macSKK/Settings/GeneralView.swift +++ b/macSKK/Settings/GeneralView.swift @@ -30,12 +30,12 @@ struct GeneralView: View { Text("AOEUIDHTN").tag("AOEUIDHTN") } Picker("Behavior of Comma", selection: $settingsViewModel.comma) { - ForEach(Comma.allCases, id: \.id) { comma in + ForEach(Punctuation.Comma.allCases, id: \.id) { comma in Text(comma.description).tag(comma) } } Picker("Behavior of Period", selection: $settingsViewModel.period) { - ForEach(Period.allCases, id: \.id) { period in + ForEach(Punctuation.Period.allCases, id: \.id) { period in Text(period.description).tag(period) } } diff --git a/macSKK/Settings/SettingsViewModel.swift b/macSKK/Settings/SettingsViewModel.swift index cc17184..4bbbff3 100644 --- a/macSKK/Settings/SettingsViewModel.swift +++ b/macSKK/Settings/SettingsViewModel.swift @@ -189,78 +189,6 @@ enum SelectingBackspace: Int, CaseIterable, Identifiable { } } -/** - * カンマを入力したときに使用する読点の設定。 - * ローマ字かな変換ルールを上書きすることが可能。 - */ -enum Comma: Int, CaseIterable, Identifiable { - typealias ID = Int - var id: ID { rawValue } - /// ローマ字かな変換ルールをそのまま適用する - case `default` = 0 - /// "、" を入力する - case ten = 1 - /// "," (全角カンマ) を入力する - case comma = 2 - - init?(rawValue: Int) { - switch rawValue & 3 { - case 0: self = .default - case 1: self = .ten - case 2: self = .comma - default: - return nil - } - } - - var description: String { - switch self { - case .default: - return String(localized: "Follow Romaji-Kana Rule") - case .ten: - return String(format: String(localized: "EnterKey"), "、") - case .comma: - return String(format: String(localized: "EnterKey"), ",") - } - } -} - -/** - * ピリオドを入力したときに使用する句点の設定。 - * ローマ字かな変換ルールを上書きすることが可能。 - */ -enum Period: Int, CaseIterable, Identifiable { - typealias ID = Int - var id: ID { rawValue } - /// ローマ字かな変換ルールをそのまま適用する - case `default` = 0 - /// "。" を入力する - case maru = 4 - /// "." (全角ピリオド) を入力する - case period = 8 - - init?(rawValue: Int) { - switch rawValue & 12 { - case 0: self = .default - case 4: self = .maru - case 8: self = .period - default: - return nil - } - } - - var description: String { - switch self { - case .default: - return String(localized: "Follow Romaji-Kana Rule") - case .maru: - return String(format: String(localized: "EnterKey"), "。") - case .period: - return String(format: String(localized: "EnterKey"), ".") - } - } -} - @MainActor final class SettingsViewModel: ObservableObject { /// CheckUpdaterで取得した最新のリリース。取得前はnil @@ -303,8 +231,8 @@ final class SettingsViewModel: ObservableObject { @Published var enterNewLine: Bool @Published var systemDict: SystemDict.Kind @Published var selectingBackspace: SelectingBackspace - @Published var period: Period - @Published var comma: Comma + @Published var period: Punctuation.Period + @Published var comma: Punctuation.Comma // 辞書ディレクトリ let dictionariesDirectoryUrl: URL @@ -358,13 +286,12 @@ final class SettingsViewModel: ObservableObject { selectCandidateKeys = UserDefaults.standard.string(forKey: UserDefaultsKeys.selectCandidateKeys)! enterNewLine = UserDefaults.standard.bool(forKey: UserDefaultsKeys.enterNewLine) selectingBackspace = SelectingBackspace(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.selectingBackspace)) ?? SelectingBackspace.default - comma = Comma(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.punctuation)) ?? .default - period = Period(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.punctuation)) ?? .default + comma = Punctuation.Comma(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.punctuation)) ?? .default + period = Punctuation.Period(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKeys.punctuation)) ?? .default Global.selectCandidateKeys = selectCandidateKeys.lowercased().map { $0 } Global.systemDict = systemDict Global.selectingBackspace = selectingBackspace - Global.comma = comma - Global.period = period + Global.punctuation = Punctuation(comma: comma, period: period) // SKK-JISYO.Lのようなファイルの読み込みが遅いのでバックグラウンドで処理 $dictSettings.filter({ !$0.isEmpty }).receive(on: DispatchQueue.global()).sink { dictSettings in @@ -541,9 +468,9 @@ final class SettingsViewModel: ObservableObject { $comma.combineLatest($period).dropFirst().sink { (comma, period) in logger.log("句読点の入力が変更されました。 カンマ: \(comma.description, privacy: .public), ピリオド: \(period.description, privacy: .public)") - Global.comma = comma - Global.period = period - UserDefaults.standard.set(comma.rawValue | period.rawValue, forKey: UserDefaultsKeys.punctuation) + let punctuation = Punctuation(comma: comma, period: period) + Global.punctuation = punctuation + UserDefaults.standard.set(punctuation.rawValue, forKey: UserDefaultsKeys.punctuation) }.store(in: &cancellables) NotificationCenter.default.publisher(for: notificationNameDictLoad).receive(on: RunLoop.main).sink { [weak self] notification in @@ -585,8 +512,8 @@ final class SettingsViewModel: ObservableObject { enterNewLine = false systemDict = .daijirin selectingBackspace = SelectingBackspace.default - comma = Comma.default - period = Period.default + comma = Punctuation.default.comma + period = Punctuation.default.period } // DictionaryViewのPreviewProvider用 diff --git a/macSKK/StateMachine.swift b/macSKK/StateMachine.swift index 23940f0..7840ffd 100644 --- a/macSKK/StateMachine.swift +++ b/macSKK/StateMachine.swift @@ -364,7 +364,7 @@ final class StateMachine { switch state.inputMode { case .hiragana, .katakana, .hankaku: if input.isAlphabet && !action.optionIsPressed() { - let result = Global.kanaRule.convert(input, comma: Global.comma, period: Global.period) + let result = Global.kanaRule.convert(input, punctuation: Global.punctuation) if let moji = result.kakutei { if action.shiftIsPressed() { state.inputMethod = .composing( @@ -389,7 +389,7 @@ final class StateMachine { cursorPosition: action.cursorPosition), specialState: specialState) } - let result = Global.kanaRule.convert(characters, comma: Global.comma, period: Global.period) + let result = Global.kanaRule.convert(characters, punctuation: Global.punctuation) if let moji = result.kakutei { if action.shiftIsPressed() && moji.kana.isHiragana { state.inputMethod = .composing( @@ -631,7 +631,7 @@ final class StateMachine { if state.inputMode == .direct { return handleComposingPrintable( input: ";", - converted: Global.kanaRule.convert(";", comma: Global.comma, period: Global.period), + converted: Global.kanaRule.convert(";", punctuation: Global.punctuation), action: action, composing: composing, specialState: specialState) @@ -759,9 +759,9 @@ final class StateMachine { if let input { let converted: Romaji.ConvertedMoji if !input.isAlphabet, let characters = action.characters() { - converted = Global.kanaRule.convert(romaji + characters, comma: Global.comma, period: Global.period) + converted = Global.kanaRule.convert(romaji + characters, punctuation: Global.punctuation) } else { - converted = Global.kanaRule.convert(romaji + input, comma: Global.comma, period: Global.period) + converted = Global.kanaRule.convert(romaji + input, punctuation: Global.punctuation) } return handleComposingPrintable( @@ -915,7 +915,7 @@ final class StateMachine { @MainActor private func useKanaRuleIfPresent(inputMode: InputMode, romaji: String, input: String) -> Romaji.ConvertedMoji? { if inputMode != .direct && !romaji.isEmpty { - let converted = Global.kanaRule.convert(romaji + input, comma: Global.comma, period: Global.period) + let converted = Global.kanaRule.convert(romaji + input, punctuation: Global.punctuation) if converted.kakutei != nil && converted.input == "" { return converted } else { diff --git a/macSKK/macSKKApp.swift b/macSKK/macSKKApp.swift index 04e3145..d9d499c 100644 --- a/macSKK/macSKKApp.swift +++ b/macSKK/macSKKApp.swift @@ -195,7 +195,7 @@ struct macSKKApp: App { UserDefaultsKeys.enterNewLine: false, UserDefaultsKeys.systemDict: SystemDict.Kind.daijirin.rawValue, UserDefaultsKeys.selectingBackspace: SelectingBackspace.default.rawValue, - UserDefaultsKeys.punctuation: Comma.default.rawValue | Period.default.rawValue, + UserDefaultsKeys.punctuation: Punctuation.default.rawValue ]) } diff --git a/macSKKTests/PunctuationTests.swift b/macSKKTests/PunctuationTests.swift new file mode 100644 index 0000000..a08c58d --- /dev/null +++ b/macSKKTests/PunctuationTests.swift @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import XCTest + +@testable import macSKK + +final class PunctuationTests: XCTestCase { + func testInit() { + guard var punctuation = Punctuation(rawValue: 0) else { XCTFail(); return } // default, default + XCTAssertEqual(punctuation.comma, .default) + XCTAssertEqual(punctuation.period, .default) + punctuation = Punctuation(comma: .comma, period: .maru) + XCTAssertEqual(punctuation.comma, .comma) + XCTAssertEqual(punctuation.period, .maru) + XCTAssertEqual(punctuation.rawValue, 256 | 2) + let punctuation2 = Punctuation(rawValue: punctuation.rawValue) + XCTAssertEqual(punctuation2?.comma, .comma) + XCTAssertEqual(punctuation2?.period, .maru) + } +} diff --git a/macSKKTests/RomajiTests.swift b/macSKKTests/RomajiTests.swift index acb78da..6852fa4 100644 --- a/macSKKTests/RomajiTests.swift +++ b/macSKKTests/RomajiTests.swift @@ -18,32 +18,32 @@ class RomajiTests: XCTestCase { func testConvert() throws { let fileURL = Bundle(for: Self.self).url(forResource: "kana-rule-for-test", withExtension: "conf")! let kanaRule = try Romaji(contentsOf: fileURL) - XCTAssertEqual(kanaRule.convert("a", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana: "あ"))) + XCTAssertEqual(kanaRule.convert("a", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana: "あ"))) // nだけではまだ「ん」になるかは確定しない (な行などに派生する可能性がある) - XCTAssertEqual(kanaRule.convert("n", comma: .default, period: .default), Romaji.ConvertedMoji(input: "n", kakutei: nil)) - XCTAssertEqual(kanaRule.convert("na", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "な"))) - XCTAssertEqual(kanaRule.convert("ga", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "g", kana: "が"))) - XCTAssertEqual(kanaRule.convert("ji", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ"))) - XCTAssertEqual(kanaRule.convert("zi", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ")), "かなが「じ」のときはfirstRomajiはj固定") - XCTAssertEqual(kanaRule.convert("nn", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) - XCTAssertEqual(kanaRule.convert("nk", comma: .default, period: .default), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) - XCTAssertEqual(kanaRule.convert("n!", comma: .default, period: .default), Romaji.ConvertedMoji(input: "!", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) - XCTAssertEqual(kanaRule.convert("kn", comma: .default, period: .default), Romaji.ConvertedMoji(input: "n", kakutei: nil)) - XCTAssertEqual(kanaRule.convert("ny", comma: .default, period: .default), Romaji.ConvertedMoji(input: "ny", kakutei: nil)) - XCTAssertEqual(kanaRule.convert("nya", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "にゃ"))) - XCTAssertEqual(kanaRule.convert("kk", comma: .default, period: .default), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "k", kana: "っ", katakana: "ッ", hankaku: "ッ", remain: "k"))) - XCTAssertEqual(kanaRule.convert("nyk", comma: .default, period: .default), Romaji.ConvertedMoji(input: "k", kakutei: nil), "続けられない子音が連続した場合は最後の子音だけ残る") - XCTAssertEqual(kanaRule.convert("z,", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "z", kana: "‥"))) - XCTAssertEqual(kanaRule.convert("x,,", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "d", kana: "だぶるかんま")), "firstRomajiはかなの一文字目から決定される") - XCTAssertEqual(kanaRule.convert("@", comma: .default, period: .default), Romaji.ConvertedMoji(input: "@", kakutei: nil), "ルールにない文字は変換されない") - XCTAssertEqual(kanaRule.convert("a;", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana:"あせみころん")), "システム用の文字を含むことができる") - XCTAssertEqual(kanaRule.convert("ca", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "k", kana:"か")), "実際に入力した一文字目(c)ではなく「か」からローマ字(k)に変換する") + XCTAssertEqual(kanaRule.convert("n", punctuation: .default), Romaji.ConvertedMoji(input: "n", kakutei: nil)) + XCTAssertEqual(kanaRule.convert("na", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "な"))) + XCTAssertEqual(kanaRule.convert("ga", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "g", kana: "が"))) + XCTAssertEqual(kanaRule.convert("ji", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ"))) + XCTAssertEqual(kanaRule.convert("zi", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "j", kana: "じ")), "かなが「じ」のときはfirstRomajiはj固定") + XCTAssertEqual(kanaRule.convert("nn", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) + XCTAssertEqual(kanaRule.convert("nk", punctuation: .default), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) + XCTAssertEqual(kanaRule.convert("n!", punctuation: .default), Romaji.ConvertedMoji(input: "!", kakutei: Romaji.Moji(firstRomaji: "n", kana: "ん"))) + XCTAssertEqual(kanaRule.convert("kn", punctuation: .default), Romaji.ConvertedMoji(input: "n", kakutei: nil)) + XCTAssertEqual(kanaRule.convert("ny", punctuation: .default), Romaji.ConvertedMoji(input: "ny", kakutei: nil)) + XCTAssertEqual(kanaRule.convert("nya", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "n", kana: "にゃ"))) + XCTAssertEqual(kanaRule.convert("kk", punctuation: .default), Romaji.ConvertedMoji(input: "k", kakutei: Romaji.Moji(firstRomaji: "k", kana: "っ", katakana: "ッ", hankaku: "ッ", remain: "k"))) + XCTAssertEqual(kanaRule.convert("nyk", punctuation: .default), Romaji.ConvertedMoji(input: "k", kakutei: nil), "続けられない子音が連続した場合は最後の子音だけ残る") + XCTAssertEqual(kanaRule.convert("z,", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "z", kana: "‥"))) + XCTAssertEqual(kanaRule.convert("x,,", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "d", kana: "だぶるかんま")), "firstRomajiはかなの一文字目から決定される") + XCTAssertEqual(kanaRule.convert("@", punctuation: .default), Romaji.ConvertedMoji(input: "@", kakutei: nil), "ルールにない文字は変換されない") + XCTAssertEqual(kanaRule.convert("a;", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "a", kana:"あせみころん")), "システム用の文字を含むことができる") + XCTAssertEqual(kanaRule.convert("ca", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "k", kana:"か")), "実際に入力した一文字目(c)ではなく「か」からローマ字(k)に変換する") // 読点 - XCTAssertEqual(kanaRule.convert(",", comma: .ten, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: "、"))) - XCTAssertEqual(kanaRule.convert(",", comma: .comma, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: ","))) + XCTAssertEqual(kanaRule.convert(",", punctuation: Punctuation(comma: .ten, period: .default)), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: "、"))) + XCTAssertEqual(kanaRule.convert(",", punctuation: Punctuation(comma: .comma, period: .default)), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ",", kana: ","))) // 句点 - XCTAssertEqual(kanaRule.convert(".", comma: .default, period: .maru), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "。"))) - XCTAssertEqual(kanaRule.convert(".", comma: .comma, period: .period), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "."))) + XCTAssertEqual(kanaRule.convert(".", punctuation: Punctuation(comma: .default, period: .maru)), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "。"))) + XCTAssertEqual(kanaRule.convert(".", punctuation: Punctuation(comma: .default, period: .period)), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: ".", kana: "."))) XCTAssertEqual(kanaRule.lowercaseMap["+"], ";") XCTAssertEqual(kanaRule.lowercaseMap[":"], ";") } @@ -52,13 +52,13 @@ class RomajiTests: XCTestCase { // AZIKのセミコロンを促音(っ)として扱う設定 let kanaRule = try Romaji(source: ";,っ") // 入力はセミコロンでもfirstRomajiは "t" になる - XCTAssertEqual(kanaRule.convert(";", comma: .default, period: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "t", kana: "っ"))) + XCTAssertEqual(kanaRule.convert(";", punctuation: .default), Romaji.ConvertedMoji(input: "", kakutei: Romaji.Moji(firstRomaji: "t", kana: "っ"))) } func testVu() throws { var kanaRule = try Romaji(source: "vu,う゛") - XCTAssertEqual(kanaRule.convert("vu", comma: .default, period: .default).kakutei?.kana, "う゛") + XCTAssertEqual(kanaRule.convert("vu", punctuation: .default).kakutei?.kana, "う゛") kanaRule = try Romaji(source: "vu,ゔ") - XCTAssertEqual(kanaRule.convert("vu", comma: .default, period: .default).kakutei?.kana, "ゔ") + XCTAssertEqual(kanaRule.convert("vu", punctuation: .default).kakutei?.kana, "ゔ") } }