Help us understand the problem. What is going on with this article?

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

More than 3 years have 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/

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした