Hammerspoonで macOS Sierra + Xcode に emacs ライクなキーバインドを設定した

  • 13
    Like
  • 0
    Comment

macOS Sierra になってから、これまで使えていた Karabiner が使えなくて非常に困った。
もともと Karabiner は Xcode で emacs ライクなキーバインドを実現するために使用していたが、
代替として Hammerspoon に移行できたのでその設定内容を記載。

動作確認環境

  • macOS Sierra 10.12.2
  • Hammerspoon 0.9.48

主な設定内容

  • C-x C-s で上書き保存(⌘+s)
  • C-x C-f で Open Quickly (⌘+shift+o)
  • C-space で set-mark 的なもの
  • 設定は Xcode 操作時のみ有効

設定ファイル

Hammerspoon の導入などは省略します。
設定ファイルの全文は Github で管理しているのでそちらを参照。

https://github.com/ryuta46/home/blob/master/init.lua

ちなみに、今は使っていませんが Karabinerの設定ファイルも同じリポジトリにあります。

https://github.com/ryuta46/home/blob/master/private.xml

設定内容をかいつまんで記載します。

C-x C-s で上書き保存(⌘+s)

C-x トリガで commandMode:enable() 関数を呼び出し、

-- command mode
hs.hotkey.new({'ctrl'}, 'x', function() commandMode:enable() end),

関数内で hs.eventtap.new にてキーダウンイベントを監視します。
登録した 2ストローク目のキー(例えば C-s)と一致した場合に該当する関数(例えば ⌘-sを発行)を呼び出します。

obj.enable = function(self)
    info(self.name.." start")
    self.commandWatcher:start()
end
obj.commandWatcher = hs.eventtap.new( {hs.eventtap.event.types.keyDown},
    function(tapEvent)
        for k,v in pairs(obj.commandTable) do
            if v.key == hs.keycodes.map[tapEvent:getKeyCode()] and isEqualTable(v.modifiers, tapEvent:getFlags()) then
                info(obj.name.." end")
                obj.commandWatcher:stop()
                if v.func then
                    v.func()
                end
                return true
            end
        end

        if obj.othersFunc then
            return othersFunc(tapEvent)
        end
    end)
return obj

このあたりの書き方は下記のページなどを参照させていただき、クラスっぽい記載にしました。
http://www.ie.u-ryukyu.ac.jp/~e085739/lua.hajime.4.html

C-space で set-mark 的なもの

上記の C-x C-s の場合と同じような記載にしましたが、
登録されている 2ストローク目のキー以外のキーが押された場合に shift を強制的に押されたような動作をするようにしました。
これにより、C-space C-f C-f などと押したときに、shift を押しながらカーソル移動したのと同じような動作をさせるようにしています。

function(tapEvent) -- force shift on
    flags = tapEvent:getFlags()
    flags.shift = true
    tapEvent:setFlags(flags)
    return false
end

ちなみにXcode でもデフォルトで C-@ でset mark できますが、選択範囲をわかりやすくしたかったのと、OSのクリップボードと共有させたかったので上記のような設定をしています。

設定は Xcode 操作時のみ有効

下記コードにより ウィンドウの切り替えを監視し、Xcode フォーカス時にのみ設定が有効になるようにしています。

hs.window.filter.new('Xcode')
    :subscribe(hs.window.filter.windowFocused,function() enableAll(xcodeBindings) end)
    :subscribe(hs.window.filter.windowUnfocused,function()
        disableAll(xcodeBindings)
        markMode:disable()
        commandMode:disable()
    end)

最後に

Xcode だけで emacs キーバインドができれば全て解決ですが、それまではこれでしのぎます。
API仕様を見た感じHammerspoon はもっといろいろなことができそうなので、今後も触っていこうかと思います。
http://www.hammerspoon.org/docs/