From 2620a3ba58acd8db94ddaa0b8da15d03b1dab74a Mon Sep 17 00:00:00 2001 From: mtgto Date: Sun, 21 Jul 2024 23:36:37 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Enter=E3=82=AD=E3=83=BC=E3=81=A7=E5=A4=89?= =?UTF-8?q?=E6=8F=9B=E5=80=99=E8=A3=9C=E3=81=AE=E7=A2=BA=E5=AE=9A=20+=20?= =?UTF-8?q?=E6=94=B9=E8=A1=8C=E3=82=82=E8=A1=8C=E3=81=86=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/Global.swift | 2 + macSKK/Settings/GeneralView.swift | 3 ++ macSKK/Settings/SettingsViewModel.swift | 10 +++- macSKK/Settings/UserDefaultsKeys.swift | 2 + macSKK/StateMachine.swift | 6 +++ macSKK/en.lproj/Localizable.strings | 1 + macSKK/ja.lproj/Localizable.strings | 1 + macSKK/macSKKApp.swift | 1 + macSKKTests/StateMachineTests.swift | 70 +++++++++++++++++++++++++ 9 files changed, 95 insertions(+), 1 deletion(-) diff --git a/macSKK/Global.swift b/macSKK/Global.swift index 8ccf4670..b978b4d4 100644 --- a/macSKK/Global.swift +++ b/macSKK/Global.swift @@ -30,6 +30,8 @@ import Combine static var keyBinding: KeyBindingSet = KeyBindingSet.defaultKeyBindingSet /// 変換候補パネルから選択するときに使用するキーの配列。英字の場合は小文字にしておくこと。 static var selectCandidateKeys: [Character] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + // Enterキーで変換候補の確定だけでなく改行も行うかどうか + static var enterNewLine: Bool! // 現在のモードを表示するパネル private let inputModePanel: InputModePanel // 変換候補を表示するパネル diff --git a/macSKK/Settings/GeneralView.swift b/macSKK/Settings/GeneralView.swift index b76532f4..4e788ba5 100644 --- a/macSKK/Settings/GeneralView.swift +++ b/macSKK/Settings/GeneralView.swift @@ -9,6 +9,9 @@ struct GeneralView: View { var body: some View { VStack { Form { + Toggle(isOn: $settingsViewModel.enterNewLine, label: { + Text("Enter Key confirms a candidate & send a newline") + }) Picker("Keyboard Layout", selection: $settingsViewModel.selectedInputSourceId) { ForEach(settingsViewModel.inputSources) { inputSource in Text(inputSource.localizedName) diff --git a/macSKK/Settings/SettingsViewModel.swift b/macSKK/Settings/SettingsViewModel.swift index 1f84b068..f73a3845 100644 --- a/macSKK/Settings/SettingsViewModel.swift +++ b/macSKK/Settings/SettingsViewModel.swift @@ -170,6 +170,8 @@ final class SettingsViewModel: ObservableObject { @Published var keyBindingSets: [KeyBindingSet] /// 現在選択中のキーバインディングのセット @Published var selectedKeyBindingSet: KeyBindingSet + /// Enterキーで変換候補の確定だけでなく改行も行うかどうか + @Published var enterNewLine: Bool // 辞書ディレクトリ let dictionariesDirectoryUrl: URL @@ -205,7 +207,7 @@ final class SettingsViewModel: ObservableObject { let customizedKeyBindingSets = UserDefaults.standard.array(forKey: UserDefaultsKeys.keyBindingSets)?.compactMap { if let dict = $0 as? [String: Any] { - KeyBindingSet(dict: $0 as! [String : Any]) + KeyBindingSet(dict: dict) } else { nil } @@ -216,6 +218,7 @@ final class SettingsViewModel: ObservableObject { self.selectedKeyBindingSet = keyBindingSets.first(where: { $0.id == selectedKeyBindingSetId }) ?? KeyBindingSet.defaultKeyBindingSet selectCandidateKeys = UserDefaults.standard.string(forKey: UserDefaultsKeys.selectCandidateKeys)! + enterNewLine = UserDefaults.standard.bool(forKey: UserDefaultsKeys.enterNewLine) Global.selectCandidateKeys = selectCandidateKeys.lowercased().map { $0 } // SKK-JISYO.Lのようなファイルの読み込みが遅いのでバックグラウンドで処理 @@ -374,6 +377,10 @@ final class SettingsViewModel: ObservableObject { } }.store(in: &cancellables) + $enterNewLine.sink { enterNewLine in + Global.enterNewLine = enterNewLine + }.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 { @@ -410,6 +417,7 @@ final class SettingsViewModel: ObservableObject { findCompletionFromAllDicts = false keyBindingSets = [KeyBindingSet.defaultKeyBindingSet] selectedKeyBindingSet = KeyBindingSet.defaultKeyBindingSet + enterNewLine = false } // DictionaryViewのPreviewProvider用 diff --git a/macSKK/Settings/UserDefaultsKeys.swift b/macSKK/Settings/UserDefaultsKeys.swift index 26df879c..1c1f1f60 100644 --- a/macSKK/Settings/UserDefaultsKeys.swift +++ b/macSKK/Settings/UserDefaultsKeys.swift @@ -24,4 +24,6 @@ struct UserDefaultsKeys { static let selectedKeyBindingSetId = "selectedKeyBindingSetId" // キーバインド設定の配列 static let keyBindingSets = "keyBindingSets" + // Enterキーで変換候補の確定 + 改行も行う + static let enterNewLine = "enterNewLine" } diff --git a/macSKK/StateMachine.swift b/macSKK/StateMachine.swift index af783094..4d090759 100644 --- a/macSKK/StateMachine.swift +++ b/macSKK/StateMachine.swift @@ -539,6 +539,9 @@ final class StateMachine { state.inputMethod = .normal addFixedText(fixedText) updateModeIfPrevModeExists() + if Global.enterNewLine { + return handle(action) + } return true case .backspace: if let newComposingState = composing.dropLast() { @@ -957,6 +960,9 @@ final class StateMachine { case .enter: // 選択中の変換候補で確定 fixCurrentSelect() + if Global.enterNewLine { + return handle(action) + } return true case .backspace: let diff: Int diff --git a/macSKK/en.lproj/Localizable.strings b/macSKK/en.lproj/Localizable.strings index fe3c97a2..b6d691a1 100644 --- a/macSKK/en.lproj/Localizable.strings +++ b/macSKK/en.lproj/Localizable.strings @@ -63,6 +63,7 @@ "Candidates font size" = "Candidates font size"; "Annotation font size" = "Annotation font size"; "Find completion from all dictionaries" = "Find completion from all dictionaries"; +"Enter Key confirms a candidate & send a newline" = "Enter Key confirms a candidate & send a newline"; "Insert Blank String" = "Insert Blank String"; "Enabled" = "Enabled"; "Disabled" = "Disabled"; diff --git a/macSKK/ja.lproj/Localizable.strings b/macSKK/ja.lproj/Localizable.strings index dc76545e..e3a6f382 100644 --- a/macSKK/ja.lproj/Localizable.strings +++ b/macSKK/ja.lproj/Localizable.strings @@ -63,6 +63,7 @@ "Candidates font size" = "変換候補のフォントサイズ"; "Annotation font size" = "注釈のフォントサイズ"; "Find completion from all dictionaries" = "ユーザー辞書だけでなくすべての辞書から補完を探す"; +"Enter Key confirms a candidate & send a newline" = "Enterキーで変換候補確定後に改行を入力"; "Insert Blank String" = "空文字挿入"; "Enabled" = "有効"; "Disabled" = "無効"; diff --git a/macSKK/macSKKApp.swift b/macSKK/macSKKApp.swift index d6104888..220d65dd 100644 --- a/macSKK/macSKKApp.swift +++ b/macSKK/macSKKApp.swift @@ -196,6 +196,7 @@ struct macSKKApp: App { UserDefaultsKeys.findCompletionFromAllDicts: false, UserDefaultsKeys.keyBindingSets: [], UserDefaultsKeys.selectedKeyBindingSetId: KeyBindingSet.defaultKeyBindingSet.id, + UserDefaultsKeys.enterNewLine: false, ]) } diff --git a/macSKKTests/StateMachineTests.swift b/macSKKTests/StateMachineTests.swift index c470a660..ac2d1094 100644 --- a/macSKKTests/StateMachineTests.swift +++ b/macSKKTests/StateMachineTests.swift @@ -22,6 +22,7 @@ final class StateMachineTests: XCTestCase { // こうしないとテストの中でGlobal.kanaRuleを書き換えるテストと一緒に走らせると違うかな変換ルールのままに実行されてしまう Global.kanaRule = Self.defaultKanaRule Global.selectCandidateKeys = "123456789".map { $0 } + Global.enterNewLine = false } @MainActor func testHandleNormalSimple() { @@ -732,6 +733,29 @@ final class StateMachineTests: XCTestCase { wait(for: [expectation], timeout: 1.0) } + @MainActor func testHandleComposingEnterNewLine() { + Global.enterNewLine = true + let stateMachine = StateMachine(initialState: IMEState(inputMode: .hiragana)) + let expectation = XCTestExpectation() + stateMachine.inputMethodEvent.collect(6).sink { events in + XCTAssertEqual(events[0], .markedText(MarkedText([.plain("k")]))) + XCTAssertEqual(events[1], .markedText(MarkedText([]))) + XCTAssertEqual(events[2], .markedText(MarkedText([.plain("n")]))) + XCTAssertEqual(events[3], .fixedText("ん")) + XCTAssertEqual(events[4], .markedText(MarkedText([.markerCompose, .plain("s")]))) + XCTAssertEqual(events[5], .markedText(MarkedText([.markerCompose, .plain("さ")]))) + expectation.fulfill() + }.store(in: &cancellables) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "k"))) + XCTAssertFalse(stateMachine.handle(enterAction)) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "n"))) + XCTAssertFalse(stateMachine.handle(enterAction)) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "s", withShift: true))) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "a"))) + XCTAssertFalse(stateMachine.handle(enterAction)) + wait(for: [expectation], timeout: 1.0) + } + @MainActor func testHandleComposingBackspace() { let stateMachine = StateMachine(initialState: IMEState(inputMode: .hiragana)) let expectation = XCTestExpectation() @@ -2185,6 +2209,26 @@ final class StateMachineTests: XCTestCase { wait(for: [expectation], timeout: 1.0) } + @MainActor func testHandleSelectingEnterNewLine() { + Global.dictionary.setEntries(["と": [Word("戸")]]) + Global.enterNewLine = true + + let stateMachine = StateMachine(initialState: IMEState(inputMode: .hiragana)) + let expectation = XCTestExpectation() + stateMachine.inputMethodEvent.collect(4).sink { events in + XCTAssertEqual(events[0], .markedText(MarkedText([.markerCompose, .plain("t")]))) + XCTAssertEqual(events[1], .markedText(MarkedText([.markerCompose, .plain("と")]))) + XCTAssertEqual(events[2], .markedText(MarkedText([.markerSelect, .emphasized("戸")]))) + XCTAssertEqual(events[3], .fixedText("戸")) + expectation.fulfill() + }.store(in: &cancellables) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "t", withShift: true))) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "o"))) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: " "))) + XCTAssertFalse(stateMachine.handle(enterAction)) + wait(for: [expectation], timeout: 1.0) + } + @MainActor func testHandleSelectingPrintableRemain() { Global.dictionary.setEntries(["あい": [Word("愛")]]) @@ -2210,6 +2254,32 @@ final class StateMachineTests: XCTestCase { wait(for: [expectation], timeout: 1.0) } + @MainActor func testHandleSelectingPrintableRemainEnterNewLine() { + Global.dictionary.setEntries(["あい": [Word("愛")]]) + Global.enterNewLine = true + + let stateMachine = StateMachine(initialState: IMEState(inputMode: .hiragana)) + let expectation = XCTestExpectation() + stateMachine.inputMethodEvent.collect(8).sink { events in + XCTAssertEqual(events[0], .markedText(MarkedText([.markerCompose, .plain("あ")]))) + XCTAssertEqual(events[1], .markedText(MarkedText([.markerCompose, .plain("あい")]))) + XCTAssertEqual(events[2], .markedText(MarkedText([.markerCompose, .plain("あいう")]))) + XCTAssertEqual(events[3], .markedText(MarkedText([.markerCompose, .plain("あい"), .cursor, .plain("う")]))) + XCTAssertEqual(events[4], .markedText(MarkedText([.markerSelect, .emphasized("愛"), .cursor, .plain("う")]))) + XCTAssertEqual(events[5], .fixedText("愛")) + XCTAssertEqual(events[6], .markedText(MarkedText([.markerCompose, .plain("う")]))) + XCTAssertEqual(events[7], .fixedText("う")) + expectation.fulfill() + }.store(in: &cancellables) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "a", withShift: true))) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "i"))) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: "u"))) + XCTAssertTrue(stateMachine.handle(leftKeyAction)) + XCTAssertTrue(stateMachine.handle(printableKeyEventAction(character: " "))) + XCTAssertFalse(stateMachine.handle(enterAction), "カーソルの右に未確定文字列が残っていても確定される") + wait(for: [expectation], timeout: 1.0) + } + @MainActor func testHandleSelectingBackspace() { Global.dictionary.setEntries(["と": [Word("戸"), Word("都")]]) From 43dd8094ac90f7d0d99f73f92098df23b77f4363 Mon Sep 17 00:00:00 2001 From: mtgto Date: Wed, 24 Jul 2024 21:22:15 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=82=BA=E3=82=AD=E3=83=BC=E5=90=8D=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Settings/GeneralView.swift | 2 +- macSKK/en.lproj/Localizable.strings | 2 +- macSKK/ja.lproj/Localizable.strings | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/macSKK/Settings/GeneralView.swift b/macSKK/Settings/GeneralView.swift index 4e788ba5..e9ca39f9 100644 --- a/macSKK/Settings/GeneralView.swift +++ b/macSKK/Settings/GeneralView.swift @@ -10,7 +10,7 @@ struct GeneralView: View { VStack { Form { Toggle(isOn: $settingsViewModel.enterNewLine, label: { - Text("Enter Key confirms a candidate & send a newline") + Text("Enter Key confirms a candidate and sends a newline") }) Picker("Keyboard Layout", selection: $settingsViewModel.selectedInputSourceId) { ForEach(settingsViewModel.inputSources) { inputSource in diff --git a/macSKK/en.lproj/Localizable.strings b/macSKK/en.lproj/Localizable.strings index b6d691a1..a6439e2e 100644 --- a/macSKK/en.lproj/Localizable.strings +++ b/macSKK/en.lproj/Localizable.strings @@ -63,7 +63,7 @@ "Candidates font size" = "Candidates font size"; "Annotation font size" = "Annotation font size"; "Find completion from all dictionaries" = "Find completion from all dictionaries"; -"Enter Key confirms a candidate & send a newline" = "Enter Key confirms a candidate & send a newline"; +"Enter Key confirms a candidate and sends a newline" = "Enter Key confirms a candidate and sends a newline"; "Insert Blank String" = "Insert Blank String"; "Enabled" = "Enabled"; "Disabled" = "Disabled"; diff --git a/macSKK/ja.lproj/Localizable.strings b/macSKK/ja.lproj/Localizable.strings index e3a6f382..4636fd21 100644 --- a/macSKK/ja.lproj/Localizable.strings +++ b/macSKK/ja.lproj/Localizable.strings @@ -63,7 +63,7 @@ "Candidates font size" = "変換候補のフォントサイズ"; "Annotation font size" = "注釈のフォントサイズ"; "Find completion from all dictionaries" = "ユーザー辞書だけでなくすべての辞書から補完を探す"; -"Enter Key confirms a candidate & send a newline" = "Enterキーで変換候補確定後に改行を入力"; +"Enter Key confirms a candidate and sends a newline" = "Enterキーで変換候補確定後に改行を入力"; "Insert Blank String" = "空文字挿入"; "Enabled" = "有効"; "Disabled" = "無効"; From 904556244801248b49119bf9be9bc0395171de77 Mon Sep 17 00:00:00 2001 From: mtgto Date: Thu, 25 Jul 2024 20:24:38 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Enter=E3=82=AD=E3=83=BC=E3=81=A7=E5=A4=89?= =?UTF-8?q?=E6=8F=9B=E5=80=99=E8=A3=9C=E3=81=AE=E7=A2=BA=E5=AE=9A=20+=20?= =?UTF-8?q?=E6=94=B9=E8=A1=8C=E3=82=82=E8=A1=8C=E3=81=86=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=E3=81=97=E3=81=9F=E3=81=A8=E3=81=8D?= =?UTF-8?q?=E3=81=AB=E3=83=AD=E3=82=B0=E3=82=92=E5=87=BA=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Settings/SettingsViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/macSKK/Settings/SettingsViewModel.swift b/macSKK/Settings/SettingsViewModel.swift index f73a3845..7fed266a 100644 --- a/macSKK/Settings/SettingsViewModel.swift +++ b/macSKK/Settings/SettingsViewModel.swift @@ -378,6 +378,7 @@ final class SettingsViewModel: ObservableObject { }.store(in: &cancellables) $enterNewLine.sink { enterNewLine in + logger.log("Enterキーで変換確定と一緒に改行する設定を\(enterNewLine ? "有効" : "無効")にしました") Global.enterNewLine = enterNewLine }.store(in: &cancellables) From 9a883350fe595eff50bce6ad56ecc6e7ebdcf039 Mon Sep 17 00:00:00 2001 From: mtgto Date: Sat, 27 Jul 2024 14:38:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Optional=E3=81=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E3=81=8C=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macSKK/Global.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/macSKK/Global.swift b/macSKK/Global.swift index b978b4d4..50490455 100644 --- a/macSKK/Global.swift +++ b/macSKK/Global.swift @@ -18,7 +18,7 @@ import Combine // マスターはSettingsViewModelがもっているが、InputControllerからAppが参照できないのでグローバル変数にコピーしている。 // FIXME: NotificationCenter経由で設定画面で変更したことを各InputControllerに通知するようにしてこの変数は消すかも。 static let directModeBundleIdentifiers = CurrentValueSubject<[String], Never>([]) - // モード変更時に空白文字を一瞬追加するワークアラウンドを適用するBundle Identifierの集合 + /// モード変更時に空白文字を一瞬追加するワークアラウンドを適用するBundle Identifierの集合 static let insertBlankStringBundleIdentifiers = CurrentValueSubject<[String], Never>([]) /// ユーザー辞書だけでなくすべての辞書から補完候補を検索するか? static let findCompletionFromAllDicts = CurrentValueSubject(false) @@ -30,13 +30,14 @@ import Combine static var keyBinding: KeyBindingSet = KeyBindingSet.defaultKeyBindingSet /// 変換候補パネルから選択するときに使用するキーの配列。英字の場合は小文字にしておくこと。 static var selectCandidateKeys: [Character] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - // Enterキーで変換候補の確定だけでなく改行も行うかどうか - static var enterNewLine: Bool! - // 現在のモードを表示するパネル + /// Enterキーで変換候補の確定だけでなく改行も行うかどうか + /// ddskkの `skk-egg-like-newline` やAquaSKKの `suppress_newline_on_commit` がfalseのときと同じ + static var enterNewLine: Bool = false + /// 現在のモードを表示するパネル private let inputModePanel: InputModePanel - // 変換候補を表示するパネル + /// 変換候補を表示するパネル private let candidatesPanel: CandidatesPanel - // 補完候補を表示するパネル + /// 補完候補を表示するパネル private let completionPanel: CompletionPanel init() {