Skip to content

feat: ピン留めプロンプトへのキーボードショートカット割り当て#293

Merged
ensan-hcl merged 10 commits intomainfrom
feat/pinned-prompt-shortcuts
Feb 21, 2026
Merged

feat: ピン留めプロンプトへのキーボードショートカット割り当て#293
ensan-hcl merged 10 commits intomainfrom
feat/pinned-prompt-shortcuts

Conversation

@nyanko3141592
Copy link
Collaborator

@nyanko3141592 nyanko3141592 commented Feb 21, 2026

Summary

  • ピン留めされたプロンプトにカスタムキーボードショートカットを割り当てる機能を追加
  • テキスト選択中にショートカットキーを押すと、対応するプロンプトでMagic Conversionが直接起動
  • ショートカットの競合検出・警告機能付き

Changes

  • Core.KeyboardShortcut / EventModifierFlags 構造体を追加(Coreモジュール)
  • KeyboardShortcutRecorder (NSViewRepresentable) を追加
  • PromptHistoryItemid / shortcut フィールドを追加(後方互換デコーダー付き)
  • PromptInputView にショートカット設定UI(ShortcutEditorSheet)を追加
  • azooKeyMacInputController にピン留めプロンプトキャッシュとショートカット検出ロジックを追加
  • PromptInputWindow.close() でフォーカスを元アプリに復元するように改善
  • メニューバーのショートカット表示を Config.TransformShortcut から動的に取得

Test plan

  • cd Core && swift test - 13テスト全パス
  • ./install.sh --ignore-lint - ビルド成功
  • テキスト選択 → Ctrl+S → PromptInputWindow 表示
  • プロンプトをピン留め → ショートカットボタン表示確認
  • ショートカット設定(例: Ctrl+J)→ テキスト選択 → Ctrl+J でプロンプト実行
  • ショートカット競合警告の表示確認
  • ウィンドウ閉じてもショートカットが保持されること

nyanko3141592 and others added 3 commits February 21, 2026 14:05
ピン留めされたプロンプトにカスタムキーボードショートカットを設定できる機能を追加。
テキスト選択時にショートカットキーで直接プロンプトを実行可能に。

- KeyboardShortcut構造体とEventModifierFlags(Core module)
- KeyboardShortcutConfigItem(TransformShortcut設定)
- KeyboardShortcutRecorder(NSViewRepresentable)
- PromptHistoryItemにIdentifiable/id/shortcutを追加(後方互換デコーダー付き)
- PromptInputViewにショートカット編集UI(ShortcutEditorSheet)
- InputControllerにピン留めキャッシュとショートカット検出
- メニュー項目のショートカットを設定から動的に取得
- PromptInputWindow.close()でフォーカス復帰
- CustomTextFieldからonCancelを削除(ESCはWindow側で処理)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- PromptInputWindow: showPromptInput時にpreviousAppを保存しclose()で復元
- KeyboardShortcutRecorder: typealiasを削除しCore.KeyboardShortcutを直接使用

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds the ability to assign custom keyboard shortcuts to pinned prompts in the azooKey input method. When a user has text selected and presses a configured shortcut, the corresponding prompt is automatically applied via Magic Conversion without opening the prompt input window.

Changes:

  • Introduced KeyboardShortcut and EventModifierFlags structures in the Core module to represent keyboard shortcuts in a platform-independent, Codable format
  • Added keyboard shortcut recording UI (KeyboardShortcutRecorder) and shortcut editor sheet to the prompt input interface
  • Extended PromptHistoryItem with id and shortcut fields, including backward-compatible decoder for existing user data
  • Implemented shortcut detection logic in azooKeyMacInputController with caching for performance and conflict resolution
  • Improved focus restoration by capturing and restoring the previous application when the prompt window closes

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
Core/Sources/Core/Configs/KeyboardShortcut.swift Defines KeyboardShortcut and EventModifierFlags structs with Codable/Sendable conformance for cross-platform keyboard shortcut representation
Core/Sources/Core/Configs/KeyboardShortcutConfigItem.swift Implements ConfigItem protocol for KeyboardShortcut, including TransformShortcut config
azooKeyMac/Configs/KeyboardShortcut.swift Provides NSEvent.ModifierFlags conversion extensions for macOS integration
azooKeyMac/Windows/KeyboardShortcutRecorder.swift Custom NSView-based keyboard shortcut recorder UI component
azooKeyMac/Windows/PromptInput/PromptHistoryItem.swift Adds id and shortcut fields with backward-compatible decoding
azooKeyMac/Windows/PromptInput/PromptInputView.swift Adds shortcut editor UI, conflict detection, and shortcut management logic
azooKeyMac/Windows/PromptInput/PromptInputWindow.swift Captures and restores focus to previous application on window close
azooKeyMac/Windows/PromptInput/CustomTextField.swift Removes Escape key handling (now handled at window level)
azooKeyMac/InputController/azooKeyMacInputController.swift Implements shortcut detection, caching, and trigger logic for custom prompts
azooKeyMac/InputController/azooKeyMacInputControllerHelper.swift Updates menu item to use dynamic shortcut from config instead of hardcoded values
azooKeyMac/InputController/azooKeyMacInputController+SelectedTextTransform.swift Reloads shortcut cache when prompt window closes to sync configuration changes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

self.applyCallback = onApply
self.completion = completion
self.initialPrompt = initialPrompt
self.previousApp = NSWorkspace.shared.frontmostApplication
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing previousApp may capture the azooKey IME itself if it's the frontmost application when showPromptInput is called. This could prevent proper focus restoration to the actual text editing application. Consider checking if frontmostApplication is the current application before storing it, or use a different approach to capture the client application context.

Suggested change
self.previousApp = NSWorkspace.shared.frontmostApplication
let frontmostApp = NSWorkspace.shared.frontmostApplication
let currentApp = NSRunningApplication.current
if let frontmostApp, frontmostApp.processIdentifier != currentApp.processIdentifier {
self.previousApp = frontmostApp
} else {
self.previousApp = nil
}

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +57
import Foundation

/// キーボードショートカットを表す構造体
public struct KeyboardShortcut: Codable, Equatable, Hashable, Sendable {
public var key: String
public var modifiers: EventModifierFlags

public init(key: String, modifiers: EventModifierFlags) {
self.key = key
self.modifiers = modifiers
}

/// デフォルトのショートカット(Control+S)
public static let defaultTransformShortcut = KeyboardShortcut(
key: "s",
modifiers: .control
)

/// 表示用の文字列(例: "⌃S")
public var displayString: String {
var result = ""

if modifiers.contains(.control) {
result += "⌃"
}
if modifiers.contains(.option) {
result += "⌥"
}
if modifiers.contains(.shift) {
result += "⇧"
}
if modifiers.contains(.command) {
result += "⌘"
}

result += key.uppercased()
return result
}
}

/// ModifierFlagsをCodable/Sendableにするためのラッパー(rawValueベース)
public struct EventModifierFlags: Codable, Equatable, Hashable, Sendable {
public var rawValue: UInt

public init(rawValue: UInt) {
self.rawValue = rawValue
}

public static let control = EventModifierFlags(rawValue: 1 << 18) // NSEvent.ModifierFlags.control.rawValue
public static let option = EventModifierFlags(rawValue: 1 << 19) // NSEvent.ModifierFlags.option.rawValue
public static let shift = EventModifierFlags(rawValue: 1 << 17) // NSEvent.ModifierFlags.shift.rawValue
public static let command = EventModifierFlags(rawValue: 1 << 20) // NSEvent.ModifierFlags.command.rawValue

public func contains(_ other: EventModifierFlags) -> Bool {
(rawValue & other.rawValue) == other.rawValue
}
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new KeyboardShortcut and EventModifierFlags structs in the Core module lack test coverage. Given that the Core module has comprehensive test coverage (as seen in InputUtilsTests, KeyMapTests, WindowsTests, etc.), new functionality should include tests. Consider adding tests for: (1) KeyboardShortcut equality and hashing, (2) EventModifierFlags bitwise operations (especially the contains method), (3) displayString formatting for various modifier combinations, and (4) Codable conformance (encoding/decoding).

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +11
// deviceIndependentFlagsMaskを適用してデバイス固有のフラグを除去し、
// サポートするモディファイア(control, option, shift, command)のみを抽出
let deviceIndependent = nsModifiers.intersection(.deviceIndependentFlagsMask)
let supportedMask: NSEvent.ModifierFlags = [.control, .option, .shift, .command]
self.init(rawValue: deviceIndependent.intersection(supportedMask).rawValue)
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EventModifierFlags init(from:) conversion may not correctly handle all NSEvent.ModifierFlags values. The deviceIndependentFlagsMask intersection is applied first, but then a second intersection with supportedMask is performed. This double intersection might inadvertently filter out valid modifier flags. Consider testing edge cases where multiple modifiers are combined, and verify that the conversion is bidirectional (nsModifierFlags property can reconstruct the original flags).

Suggested change
// deviceIndependentFlagsMaskを適用してデバイス固有のフラグを除去し、
// サポートするモディファイア(control, option, shift, command)のみを抽出
let deviceIndependent = nsModifiers.intersection(.deviceIndependentFlagsMask)
let supportedMask: NSEvent.ModifierFlags = [.control, .option, .shift, .command]
self.init(rawValue: deviceIndependent.intersection(supportedMask).rawValue)
// サポートするモディファイア(control, option, shift, command)のみを抽出
let supportedMask: NSEvent.ModifierFlags = [.control, .option, .shift, .command]
let filtered = nsModifiers.intersection(supportedMask)
self.init(rawValue: filtered.rawValue)

Copilot uses AI. Check for mistakes.
- ショートカットマッチ時にイベントを確実に消費するよう修正
- previousAppが自アプリをキャプチャしないようガード追加
- ModifierFlags変換の冗長な二重intersectionを簡素化
- KeyboardShortcutRecorderでplaceholderプロパティを使用
- reservedShortcutsをcomputed propertyに変更
- 警告メッセージの文法修正

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…のEscapeキー処理を復元

- EventModifierFlags を削除し、既存の KeyEventCore.ModifierFlag を使用するよう統一
- KeyEventCore.ModifierFlag に Codable 準拠を追加
- PR #277 で追加された CustomTextField の onCancel/Escape キー処理を復元

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…集約、キャンセル時フォーカス復元を復元

- KeyboardShortcut構造体をKeyboardShortcutConfigItem.swiftに統合しファイル削除
- azooKeyMac/Configs/KeyboardShortcut.swiftを削除し、NSEvent.swiftにModifierFlag変換を集約
- PR #277のキャンセル時フォーカス復元ロジックを復元

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +12 to +19
// ショートカット設定を読み込み
let shortcut = Config.TransformShortcut().value
self.transformSelectedTextMenuItem = NSMenuItem(
title: TransformMenuTitle.normal,
action: #selector(self.performTransformSelectedText(_:)),
keyEquivalent: shortcut.key
)
self.transformSelectedTextMenuItem.keyEquivalentModifierMask = shortcut.modifiers.nsModifierFlags
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これって何が発生することを期待してる変更?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

menuitemがどこから来るのかわからない

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ユーザーがいい感じ変換のショートカットをカスタマイズした場合に、メニューバーの表示も連動して変わるようにするようにしたかった。現状はいい感じ変換自体(ctrl + s)自体の変更自体はまだできないので、それを将来的に入れるための変更として追加している。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど。うーん、ちょっと文脈が混ざって嫌なので一旦この差分はなしにしたいかも

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


/// ピン留めプロンプトのキャッシュを更新
func reloadPinnedPromptsCache() {
guard let data = UserDefaults.standard.data(forKey: "dev.ensan.inputmethod.azooKeyMac.preference.PromptHistory"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここでUserDefaultsからキーをベタ書きして読み出すのはちょっと良くない気がする。そもそもわざわざデコードをmanualで書く必要って何があるんだっけ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringConfigItemはString型なので、一旦manuaでPromptHistoryItemの配列としてデコードする必要があったけどConfig.PromptHistory.key でキー文字列だけ共通化すれば別にいらないなあ。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

じゃあキー文字列だけ共通化してほしい

nyanko3141592 and others added 2 commits February 21, 2026 18:18
Copy link
Member

@ensan-hcl ensan-hcl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

大体いいかな、あとlint通ったらLGTM

nyanko3141592 and others added 2 commits February 21, 2026 18:23
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ensan-hcl ensan-hcl merged commit caf404b into main Feb 21, 2026
8 checks passed
@ensan-hcl ensan-hcl deleted the feat/pinned-prompt-shortcuts branch February 21, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants