Edited at

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

More than 1 year has passed since last update.

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/