Skip to content
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
6 changes: 6 additions & 0 deletions Core/Sources/Core/Configs/BoolConfigItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ extension Config {
static let `default` = false
public static let key: String = "dev.ensan.inputmethod.azooKeyMac.preference.typeHalfSpace"
}
/// Optionキー押下時に直接全角英数を入力する設定
public struct OptionDirectFullWidthInput: BoolConfigItem {
public init() {}
static let `default` = false
public static let key: String = "dev.ensan.inputmethod.azooKeyMac.preference.optionDirectFullWidthInput"
}
/// AI変換時にコンテキストを含めるかどうか
public struct IncludeContextInAITransform: BoolConfigItem {
public init() {}
Expand Down
43 changes: 43 additions & 0 deletions Core/Sources/Core/InputUtils/OptionDirectInputResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Foundation

public enum OptionDirectInputResolver {
public static func resolve(
characters: String?,
modifierFlags: KeyEventCore.ModifierFlag,
inputLanguage: InputLanguage,
inputState: InputState,
typeBackSlash: Bool
) -> String? {
guard inputLanguage == .japanese, inputState == .none else {
return nil
}
guard modifierFlags == [.option] || modifierFlags == [.option, .shift] else {
return nil
}
guard let characters,
!characters.isEmpty,
isPrintable(characters)
else {
return nil
}
let normalized = normalize(characters, typeBackSlash: typeBackSlash)
return normalized.applyingTransform(.fullwidthToHalfwidth, reverse: true)
}

private static func isPrintable(_ text: String) -> Bool {
let printable: CharacterSet = [.alphanumerics, .symbols, .punctuationCharacters]
.reduce(into: CharacterSet()) {
$0.formUnion($1)
}
return CharacterSet(text.unicodeScalars).isSubset(of: printable)
}

private static func normalize(_ text: String, typeBackSlash: Bool) -> String {
switch text {
case "¥", "\\":
typeBackSlash ? "\\" : "¥"
default:
text
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Core
import Testing

@Test func testOptionDirectInputResolverReturnsFullWidthTextForJapaneseNoneState() async throws {
let option: KeyEventCore.ModifierFlag = [.option]
let shiftOption: KeyEventCore.ModifierFlag = [.option, .shift]

#expect(OptionDirectInputResolver.resolve(
characters: "a",
modifierFlags: option,
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: false
) == "a")
#expect(OptionDirectInputResolver.resolve(
characters: "A",
modifierFlags: shiftOption,
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: false
) == "A")
#expect(OptionDirectInputResolver.resolve(
characters: "-",
modifierFlags: option,
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: false
) == "-")
#expect(OptionDirectInputResolver.resolve(
characters: "/",
modifierFlags: shiftOption,
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: false
) == "/")
#expect(OptionDirectInputResolver.resolve(
characters: "¥",
modifierFlags: option,
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: false
) == "¥")
#expect(OptionDirectInputResolver.resolve(
characters: "¥",
modifierFlags: option,
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: true
) == "\")
}

@Test func testOptionDirectInputResolverRejectsUnsupportedContext() async throws {
let option: KeyEventCore.ModifierFlag = [.option]

#expect(OptionDirectInputResolver.resolve(
characters: "a",
modifierFlags: [],
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: false
) == nil)
#expect(OptionDirectInputResolver.resolve(
characters: "a",
modifierFlags: option,
inputLanguage: .english,
inputState: .none,
typeBackSlash: false
) == nil)
#expect(OptionDirectInputResolver.resolve(
characters: "a",
modifierFlags: option,
inputLanguage: .japanese,
inputState: .composing,
typeBackSlash: false
) == nil)
#expect(OptionDirectInputResolver.resolve(
characters: "\r",
modifierFlags: option,
inputLanguage: .japanese,
inputState: .none,
typeBackSlash: false
) == nil)
}
22 changes: 14 additions & 8 deletions azooKeyMac/InputController/azooKeyMacInputController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,6 @@ class azooKeyMacInputController: IMKInputController, NSMenuItemValidation { // s
self.appMenu
}

private func isPrintable(_ text: String) -> Bool {
let printable: CharacterSet = [.alphanumerics, .symbols, .punctuationCharacters]
.reduce(into: CharacterSet()) {
$0.formUnion($1)
}
return CharacterSet(text.unicodeScalars).isSubset(of: printable)
}

// swiftlint:disable:next cyclomatic_complexity
@MainActor override func handle(_ event: NSEvent!, client sender: Any!) -> Bool {
guard let event, let client = sender as? IMKTextInput else {
Expand All @@ -287,6 +279,20 @@ class azooKeyMacInputController: IMKInputController, NSMenuItemValidation { // s
return true
}

let eventModifiers = KeyEventCore.ModifierFlag(from: event.modifierFlags)
let charactersForOptionDirectInput = event.characters(byApplyingModifiers: event.modifierFlags.subtracting(.option))
if Config.OptionDirectFullWidthInput().value,
let text = OptionDirectInputResolver.resolve(
characters: charactersForOptionDirectInput,
modifierFlags: eventModifiers,
inputLanguage: inputLanguage,
inputState: inputState,
typeBackSlash: Config.TypeBackSlash().value
) {
client.insertText(text, replacementRange: NSRange(location: NSNotFound, length: 0))
return true
}

let userAction = UserAction.getUserAction(eventCore: event.keyEventCore, inputLanguage: inputLanguage)

// 英数キー(keyCode 102)の処理
Expand Down
2 changes: 2 additions & 0 deletions azooKeyMac/Windows/ConfigWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct ConfigWindow: View {
@ConfigState private var typeBackSlash = Config.TypeBackSlash()
@ConfigState private var punctuationStyle = Config.PunctuationStyle()
@ConfigState private var typeHalfSpace = Config.TypeHalfSpace()
@ConfigState private var optionDirectFullWidthInput = Config.OptionDirectFullWidthInput()
@ConfigState private var zenzaiProfile = Config.ZenzaiProfile()
@ConfigState private var zenzaiPersonalizationLevel = Config.ZenzaiPersonalizationLevel()
@ConfigState private var openAiApiKey = Config.OpenAiApiKey()
Expand Down Expand Up @@ -473,6 +474,7 @@ struct ConfigWindow: View {
Section {
Toggle("円記号の代わりにバックスラッシュを入力", isOn: $typeBackSlash)
Toggle("スペースは常に半角を入力", isOn: $typeHalfSpace)
Toggle("Optionキーで直接全角英数を入力", isOn: $optionDirectFullWidthInput)
Picker("句読点の種類", selection: $punctuationStyle) {
Text("、と。").tag(Config.PunctuationStyle.Value.`kutenAndToten`)
Text("、と.").tag(Config.PunctuationStyle.Value.periodAndToten)
Expand Down
Loading