Skip to content

Commit 35d2834

Browse files
committed
feat: Option入力で全角化するモードを追加
1 parent eb39ebd commit 35d2834

5 files changed

Lines changed: 148 additions & 8 deletions

File tree

Core/Sources/Core/Configs/BoolConfigItem.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ extension Config {
5656
static let `default` = false
5757
public static let key: String = "dev.ensan.inputmethod.azooKeyMac.preference.typeHalfSpace"
5858
}
59+
/// Optionキー押下時に直接全角英数を入力する設定
60+
public struct OptionDirectFullWidthInput: BoolConfigItem {
61+
public init() {}
62+
static let `default` = false
63+
public static let key: String = "dev.ensan.inputmethod.azooKeyMac.preference.optionDirectFullWidthInput"
64+
}
5965
/// AI変換時にコンテキストを含めるかどうか
6066
public struct IncludeContextInAITransform: BoolConfigItem {
6167
public init() {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Foundation
2+
3+
public enum OptionDirectInputResolver {
4+
public static func resolve(
5+
characters: String?,
6+
modifierFlags: KeyEventCore.ModifierFlag,
7+
inputLanguage: InputLanguage,
8+
inputState: InputState,
9+
typeBackSlash: Bool
10+
) -> String? {
11+
guard inputLanguage == .japanese, inputState == .none else {
12+
return nil
13+
}
14+
guard modifierFlags == [.option] || modifierFlags == [.option, .shift] else {
15+
return nil
16+
}
17+
guard let characters,
18+
!characters.isEmpty,
19+
isPrintable(characters)
20+
else {
21+
return nil
22+
}
23+
let normalized = normalize(characters, typeBackSlash: typeBackSlash)
24+
return normalized.applyingTransform(.fullwidthToHalfwidth, reverse: true)
25+
}
26+
27+
private static func isPrintable(_ text: String) -> Bool {
28+
let printable: CharacterSet = [.alphanumerics, .symbols, .punctuationCharacters]
29+
.reduce(into: CharacterSet()) {
30+
$0.formUnion($1)
31+
}
32+
return CharacterSet(text.unicodeScalars).isSubset(of: printable)
33+
}
34+
35+
private static func normalize(_ text: String, typeBackSlash: Bool) -> String {
36+
switch text {
37+
case "¥", "\\":
38+
typeBackSlash ? "\\" : "¥"
39+
default:
40+
text
41+
}
42+
}
43+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Core
2+
import Testing
3+
4+
@Test func testOptionDirectInputResolverReturnsFullWidthTextForJapaneseNoneState() async throws {
5+
let option: KeyEventCore.ModifierFlag = [.option]
6+
let shiftOption: KeyEventCore.ModifierFlag = [.option, .shift]
7+
8+
#expect(OptionDirectInputResolver.resolve(
9+
characters: "a",
10+
modifierFlags: option,
11+
inputLanguage: .japanese,
12+
inputState: .none,
13+
typeBackSlash: false
14+
) == "")
15+
#expect(OptionDirectInputResolver.resolve(
16+
characters: "A",
17+
modifierFlags: shiftOption,
18+
inputLanguage: .japanese,
19+
inputState: .none,
20+
typeBackSlash: false
21+
) == "")
22+
#expect(OptionDirectInputResolver.resolve(
23+
characters: "-",
24+
modifierFlags: option,
25+
inputLanguage: .japanese,
26+
inputState: .none,
27+
typeBackSlash: false
28+
) == "")
29+
#expect(OptionDirectInputResolver.resolve(
30+
characters: "/",
31+
modifierFlags: shiftOption,
32+
inputLanguage: .japanese,
33+
inputState: .none,
34+
typeBackSlash: false
35+
) == "")
36+
#expect(OptionDirectInputResolver.resolve(
37+
characters: "¥",
38+
modifierFlags: option,
39+
inputLanguage: .japanese,
40+
inputState: .none,
41+
typeBackSlash: false
42+
) == "")
43+
#expect(OptionDirectInputResolver.resolve(
44+
characters: "¥",
45+
modifierFlags: option,
46+
inputLanguage: .japanese,
47+
inputState: .none,
48+
typeBackSlash: true
49+
) == "")
50+
}
51+
52+
@Test func testOptionDirectInputResolverRejectsUnsupportedContext() async throws {
53+
let option: KeyEventCore.ModifierFlag = [.option]
54+
55+
#expect(OptionDirectInputResolver.resolve(
56+
characters: "a",
57+
modifierFlags: [],
58+
inputLanguage: .japanese,
59+
inputState: .none,
60+
typeBackSlash: false
61+
) == nil)
62+
#expect(OptionDirectInputResolver.resolve(
63+
characters: "a",
64+
modifierFlags: option,
65+
inputLanguage: .english,
66+
inputState: .none,
67+
typeBackSlash: false
68+
) == nil)
69+
#expect(OptionDirectInputResolver.resolve(
70+
characters: "a",
71+
modifierFlags: option,
72+
inputLanguage: .japanese,
73+
inputState: .composing,
74+
typeBackSlash: false
75+
) == nil)
76+
#expect(OptionDirectInputResolver.resolve(
77+
characters: "\r",
78+
modifierFlags: option,
79+
inputLanguage: .japanese,
80+
inputState: .none,
81+
typeBackSlash: false
82+
) == nil)
83+
}

azooKeyMac/InputController/azooKeyMacInputController.swift

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,6 @@ class azooKeyMacInputController: IMKInputController, NSMenuItemValidation { // s
255255
self.appMenu
256256
}
257257

258-
private func isPrintable(_ text: String) -> Bool {
259-
let printable: CharacterSet = [.alphanumerics, .symbols, .punctuationCharacters]
260-
.reduce(into: CharacterSet()) {
261-
$0.formUnion($1)
262-
}
263-
return CharacterSet(text.unicodeScalars).isSubset(of: printable)
264-
}
265-
266258
// swiftlint:disable:next cyclomatic_complexity
267259
@MainActor override func handle(_ event: NSEvent!, client sender: Any!) -> Bool {
268260
guard let event, let client = sender as? IMKTextInput else {
@@ -287,6 +279,20 @@ class azooKeyMacInputController: IMKInputController, NSMenuItemValidation { // s
287279
return true
288280
}
289281

282+
let eventModifiers = KeyEventCore.ModifierFlag(from: event.modifierFlags)
283+
let charactersForOptionDirectInput = event.characters(byApplyingModifiers: event.modifierFlags.subtracting(.option))
284+
if Config.OptionDirectFullWidthInput().value,
285+
let text = OptionDirectInputResolver.resolve(
286+
characters: charactersForOptionDirectInput,
287+
modifierFlags: eventModifiers,
288+
inputLanguage: inputLanguage,
289+
inputState: inputState,
290+
typeBackSlash: Config.TypeBackSlash().value
291+
) {
292+
client.insertText(text, replacementRange: NSRange(location: NSNotFound, length: 0))
293+
return true
294+
}
295+
290296
let userAction = UserAction.getUserAction(eventCore: event.keyEventCore, inputLanguage: inputLanguage)
291297

292298
// 英数キー(keyCode 102)の処理

azooKeyMac/Windows/ConfigWindow.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct ConfigWindow: View {
88
@ConfigState private var typeBackSlash = Config.TypeBackSlash()
99
@ConfigState private var punctuationStyle = Config.PunctuationStyle()
1010
@ConfigState private var typeHalfSpace = Config.TypeHalfSpace()
11+
@ConfigState private var optionDirectFullWidthInput = Config.OptionDirectFullWidthInput()
1112
@ConfigState private var zenzaiProfile = Config.ZenzaiProfile()
1213
@ConfigState private var zenzaiPersonalizationLevel = Config.ZenzaiPersonalizationLevel()
1314
@ConfigState private var openAiApiKey = Config.OpenAiApiKey()
@@ -473,6 +474,7 @@ struct ConfigWindow: View {
473474
Section {
474475
Toggle("円記号の代わりにバックスラッシュを入力", isOn: $typeBackSlash)
475476
Toggle("スペースは常に半角を入力", isOn: $typeHalfSpace)
477+
Toggle("Optionキーで直接全角英数を入力", isOn: $optionDirectFullWidthInput)
476478
Picker("句読点の種類", selection: $punctuationStyle) {
477479
Text("、と。").tag(Config.PunctuationStyle.Value.`kutenAndToten`)
478480
Text("、と.").tag(Config.PunctuationStyle.Value.periodAndToten)

0 commit comments

Comments
 (0)