Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enterキーで変換候補の確定 + 改行も行う設定を追加 #190

Merged
merged 4 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions macSKK/Global.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bool, Never>(false)
Expand All @@ -30,11 +30,14 @@ import Combine
static var keyBinding: KeyBindingSet = KeyBindingSet.defaultKeyBindingSet
/// 変換候補パネルから選択するときに使用するキーの配列。英字の場合は小文字にしておくこと。
static var selectCandidateKeys: [Character] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
// 現在のモードを表示するパネル
/// 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() {
Expand Down
3 changes: 3 additions & 0 deletions macSKK/Settings/GeneralView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ struct GeneralView: View {
var body: some View {
VStack {
Form {
Toggle(isOn: $settingsViewModel.enterNewLine, label: {
Text("Enter Key confirms a candidate and sends a newline")
})
Picker("Keyboard Layout", selection: $settingsViewModel.selectedInputSourceId) {
ForEach(settingsViewModel.inputSources) { inputSource in
Text(inputSource.localizedName)
Expand Down
11 changes: 10 additions & 1 deletion macSKK/Settings/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ final class SettingsViewModel: ObservableObject {
@Published var keyBindingSets: [KeyBindingSet]
/// 現在選択中のキーバインディングのセット
@Published var selectedKeyBindingSet: KeyBindingSet
/// Enterキーで変換候補の確定だけでなく改行も行うかどうか
@Published var enterNewLine: Bool

// 辞書ディレクトリ
let dictionariesDirectoryUrl: URL
Expand Down Expand Up @@ -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
}
Expand All @@ -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のようなファイルの読み込みが遅いのでバックグラウンドで処理
Expand Down Expand Up @@ -374,6 +377,11 @@ final class SettingsViewModel: ObservableObject {
}
}.store(in: &cancellables)

$enterNewLine.sink { enterNewLine in
logger.log("Enterキーで変換確定と一緒に改行する設定を\(enterNewLine ? "有効" : "無効")にしました")
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 {
Expand Down Expand Up @@ -410,6 +418,7 @@ final class SettingsViewModel: ObservableObject {
findCompletionFromAllDicts = false
keyBindingSets = [KeyBindingSet.defaultKeyBindingSet]
selectedKeyBindingSet = KeyBindingSet.defaultKeyBindingSet
enterNewLine = false
}

// DictionaryViewのPreviewProvider用
Expand Down
2 changes: 2 additions & 0 deletions macSKK/Settings/UserDefaultsKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ struct UserDefaultsKeys {
static let selectedKeyBindingSetId = "selectedKeyBindingSetId"
// キーバインド設定の配列
static let keyBindingSets = "keyBindingSets"
// Enterキーで変換候補の確定 + 改行も行う
static let enterNewLine = "enterNewLine"
}
6 changes: 6 additions & 0 deletions macSKK/StateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -957,6 +960,9 @@ final class StateMachine {
case .enter:
// 選択中の変換候補で確定
fixCurrentSelect()
if Global.enterNewLine {
return handle(action)
}
return true
case .backspace:
let diff: Int
Expand Down
1 change: 1 addition & 0 deletions macSKK/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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 and sends a newline" = "Enter Key confirms a candidate and sends a newline";
"Insert Blank String" = "Insert Blank String";
"Enabled" = "Enabled";
"Disabled" = "Disabled";
Expand Down
1 change: 1 addition & 0 deletions macSKK/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"Candidates font size" = "変換候補のフォントサイズ";
"Annotation font size" = "注釈のフォントサイズ";
"Find completion from all dictionaries" = "ユーザー辞書だけでなくすべての辞書から補完を探す";
"Enter Key confirms a candidate and sends a newline" = "Enterキーで変換候補確定後に改行を入力";
"Insert Blank String" = "空文字挿入";
"Enabled" = "有効";
"Disabled" = "無効";
Expand Down
1 change: 1 addition & 0 deletions macSKK/macSKKApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ struct macSKKApp: App {
UserDefaultsKeys.findCompletionFromAllDicts: false,
UserDefaultsKeys.keyBindingSets: [],
UserDefaultsKeys.selectedKeyBindingSetId: KeyBindingSet.defaultKeyBindingSet.id,
UserDefaultsKeys.enterNewLine: false,
])
}

Expand Down
70 changes: 70 additions & 0 deletions macSKKTests/StateMachineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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("愛")]])

Expand All @@ -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("都")]])

Expand Down
Loading