[[🦉JINRAI]]のプランメモ。 > [!left-bubble] ![[chappy.webp]] [[Codex CLI]] x [[GPT-5.3-Codex]] のプランです。 ## Window Hints: 修飾キー指定で「現在ウィンドウと対象ウィンドウの位置・サイズ入れ替え」機能を追加 ### 概要 Window Hints でヒントキー確定時に、指定した修飾キー条件を満たす場合のみ、現在アクティブなウィンドウと対象ウィンドウの frame(位置・サイズ)を入れ替える。 デフォルトは無効。ユーザーは設定で修飾キーを自由に選べるようにする。 ### 追加・変更する公開IF 1. window_hints オプションに新規追加 - swapWindowFrameSelectModifiers - 型: nil | string[] - デフォルト: nil(無効) - 例: - { "shift" } - { "ctrl" } - { "cmd", "alt" } 2. 旧案の swapWindowFrameOnShiftSelect は採用しない(導入しない) ### オプション仕様(決定事項) 1. swapWindowFrameSelectModifiers が nil の場合 - 常に通常選択(現行挙動) 2. swapWindowFrameSelectModifiers が配列の場合 - 各要素は hs.hotkey で使う修飾キー文字列(shift / ctrl / alt / cmd / fn)を想定 - 入力は小文字へ正規化 - 重複はエラー(設定ミスを早期検出) - 空配列はエラー(意図不明を防ぐ) 3. 発動条件 - 「確定に使ったキーイベント」の押下修飾キー集合と swapWindowFrameSelectModifiers が完全一致した場合のみ swap - 完全一致のため、余計な修飾キーが同時押しされている場合は swap しない ### 実装方針(ファイル別) 1. window_hints.lua - DEFAULT_CONFIG に swapWindowFrameSelectModifiers = nil を追加 - normalize... 系に修飾キー配列の正規化・検証関数を追加 - modal:bind を「想定修飾キー組み合わせごと」に明示バインドする構造へ変更 - handleInputKey に「今回の入力で swap 対象かどうか」の情報を渡せるようにする - selectWindow(win, opts) に opts.swapWithFocused を追加し、true 時のみ swap ロジック実行 2. swap ロジック詳細(selectWindow 内) - focused = hs.window.focusedWindow() を取得 - ガード: - focused or win が nil -> 通常選択へフォールバック - focused:id() == win:id() -> 通常選択 - どちらかの frame() が取れない -> 通常選択 - 実行順: - focusedFrame = focused:frame() - targetFrame = win:frame() - focused:setFrame(targetFrame) - win:setFrame(focusedFrame) - win:focus() - 既存後処理(centerCursor, onSelect(win), closeHints(true))は現行同等で維持 3. 入力バインドの設計 - 既存: modal:bind({}, key, ...) と modal:bind({ "shift" }, key, ...) - 変更後: - 通常入力用: {} - swap入力用: normalizedSwapModifiers - 上記2種類を同一キーへバインドし、それぞれ handleInputKey(key, isSwap) で処理 - #key == 1 条件は継続(文字キーだけ修飾付きバインドを作る) ### 影響範囲 - 対象: ヒントキーによるウィンドウ確定 - 非対象: focusBackKey, directionKeys, escape, delete の挙動 - 既存設定との後方互換: - 既存ユーザーは設定追加しない限り一切変化なし ### README更新 1. README.md - Window Hints Options に swapWindowFrameSelectModifiers を追加 - 説明: - nil で無効 - 指定修飾キー一致時に frame swap - 完全一致条件で判定 2. README.ja.md - 同内容を日本語で追加 - 設定例に { "shift" } の例を追記 ### テスト計画 1. spec/window_hints_spec.lua にユニットテスト追加(_test 公開ヘルパーを必要最小限追加) - 修飾キー設定の正規化 - nil は許容 - 小文字化 - 重複エラー - 空配列エラー - swap判定 - 設定 nil なら false - 完全一致で true - 余分修飾があれば false - swap実行 - 正常時に双方 setFrame が呼ばれる - 同一ウィンドウ時は呼ばれない - frame取得不可時は呼ばれない - 実行後に対象ウィンドウへ focus される 2. 回帰 - 既存 window_hints_spec.lua の既存ケースが維持されること - リポジトリルートで busted を実行し全体パス ### 受け入れ基準 1. デフォルト状態(swapWindowFrameSelectModifiers=nil)で現行挙動と完全一致 2. swapWindowFrameSelectModifiers を設定したときのみ、該当修飾キー一致で swap が発動 3. swap 後は対象ウィンドウがアクティブ 4. focusBackKey / directionKeys / 通常選択の挙動に回帰なし 5. README(英日)に新オプションが明記されている ### 前提・デフォルト - Hammerspoon 側で frame() / setFrame() / focus() が利用可能であること - swap不能なケースはエラーを投げず通常選択へフォールバック - onSelect のシグネチャは変更しない(onSelect(win) のまま) ## Window Hints: focusBackKey / directionKeys でも swap 修飾を有効化(A: トグル優先) ### 概要 既存の swapWindowFrameSelectModifiers をヒント確定だけでなく、focusBackKey と directionKeys にも適用する。 focusBackKey + swap は トグル優先 で実装し、要件どおり以下を保証する。 - 例: current=A, previous=B で swap+focusBack - A と B の frame を入れ替える - B をアクティブにする - 直前履歴を A にする(次回 focusBack で戻れる) ### 変更対象 - window_hints.lua - spec/window_hints_spec.lua - 必要なら README(仕様追記のみ) ### 実装方針 1. 入力経路の統一 - handleInputKey(key, inputModifiers) で先に swapWithFocused を算出。 - その値を hint / direction / focusBack すべてへ渡す。 2. directionKeys の swap 対応 - runDirectionalAction(direction, swapWithFocused) に拡張。 - target 決定後は selectWindow(target, { swapWithFocused = swapWithFocused }) を使う。 - 既存のターゲット探索優先順位は不変。 3. focusBackKey の swap 対応(A: トグル優先) - runFocusBackAction(swapWithFocused) に拡張。 - swapWithFocused=false: 現行どおり focusHistory:focusBack()。 - swapWithFocused=true: - prev = focusHistory:getPreviousWindow() を取得。 - prev が有効なら selectWindow(prev, { swapWithFocused = true }) を実行。 - これにより OS のフォーカスイベントで current/previous が自然に反転し、トグル性を維持。 - prev 無効時は focusBack() フォールバック(既存互換)。 4. 修飾キーバインドの適用範囲拡張 - 現在の「文字キー限定」の追加修飾バインドを廃止。 - focusBackKey / directionKeys に対しても swapWindowFrameSelectModifiers の組をバインド。 - 既存互換のため {} バインドは維持。 5. 既存 swap 実装の利用 - frame 適用は既存の setFrameWithWorkarounds(..., 0) 優先ロジックを流用。 - 失敗時フォールバック/ロールバック方針は現状維持。 ### 公開IF - 追加なし(既存 swapWindowFrameSelectModifiers を流用)。 - 意味拡張: - これまで「ヒント確定のみ」だった適用対象を - 「ヒント確定 + focusBackKey + directionKeys」へ拡張。 ### テスト計画 1. window_hints_spec.lua に追加 - directionKeys 経路で swapWithFocused=true が選択処理に伝播すること。 - focusBackKey 経路で swapWithFocused=true 時に getPreviousWindow() 優先分岐へ入ること。 - focusBackKey + swap の要件シナリオ(A/B 入替・B active・prev=A)をモックで検証。 - swapWindowFrameSelectModifiers が focusBackKey / directionKeys 入力でも判定されること。 2. 回帰 - 既存 swap テスト(workaround優先、duration=0、異常系)維持。 - 全体 busted 成功。 ### 受け入れ基準 - swapWindowFrameSelectModifiers=nil では既存挙動と完全一致。 - directionKeys + swap修飾 で frame swap + 対象フォーカスが実行される。 - focusBackKey + swap修飾 で要件どおり A/B 入替後に B active、履歴 prev=A になる。 - アニメーションなし(swap適用は duration=0)。 - busted 全緑。 ### 前提・デフォルト - focusHistory が有効な場合に focusBackKey は動作(現状仕様どおり)。 - focusHistory:getPreviousWindow() が nil の場合は安全に既存 focusBack() へフォールバック。 - 競合キー解決(hintChars 予約除外)ルールは変更しない。