【hammerspoon】emacs の操作を表示する

Hammerspoonでemacsの操作を表示する

このあいだ、ひっさしぶりに emacs を起動してみて、ほんと驚いた。
 操作方法を綺麗さっぱり忘れてしまっているのである。
 前に使っていた Macbook のディスプレイが割れてしまったころ、しょうがないから Windows のノートに Linux の feren OS というのをぶちこんで使っていた。そのあいだ一ヶ月くらい、ずっと emacs を使っていたと思う。基本操作くらいは指に染みついていると思っていた。染みちゃいねえ!

おそるべきことだ。これが老化なのである。ハゲの進行具合とか下半身の活力とか、そんなのどうでもよくて、覚えたことがツルっと消えて、とっかかりも思い出せない。これがおじさんなのだ。わしゃ隠居したいよ、とほほ。

Hammerspoon で操作方法を表示する

そもそも emacs って、覚えなくちゃならないことが多すぎる。
 なにが C-x だよ。なにが M-x だっていってんだよ。
 なので、もう org-mode だけしか使わないことにする。覚えなきゃならない範囲を絞るわけだ。dired でさえ特別なコマンドは覚えない。
 でもなぁ。emacs ってついついイジりたくなっちゃうんだよなー。

そうじゃなく、そもそも覚えない、というのはどうだ。
 すぐに表示できるチートシートがあれば、なにも覚えなくてもいいはず。

ということで、Hammerspoon でチートシートを表示することにした。

チートシートのテキストファイル

手始めにテキストファイルにチートシートを書いた。
 筆者の場合はこんな感じ。本当に基本的なことを覚えられていない。
 ひとまず、「書類」フォルダのなかに「emacsKey.txt」という名前でぶちこんで置いた。

[ バッファ操作 ]
--------------
全バッファのセーブ ___ C-x s
別名でセーブ ________ C-x C-w
バッファを削除 ______ C-x k

[ 検索 ]
-------
後方検索 ___ C-r
前方検索 ___ C-s

[ コピペ ]
---------
マークをセット ___ C-@
全選択 _________ C-x h
範囲をカット _____ C-w
コピー __________ M-w
貼り付け ________ C-y

[ ウィンドウ ]
------------
現在のウィンドウを消す ___ C-x 0
他のウィンドウを消す _____ C-x 1

[ やり直し ]
------------            
アンドゥ ___ C-/

[ org-mode ]
------------
見出し作成 ___ M-ret
次の見出し ___ C-c C-n
前の見出し ___ C-c C-p

項目入れ替え ______ M-↑ M-↓
アスタリスク増減 ___ M-← M-→

項目カット ____ C-c C-x C-w
項目ペースト ___ C-c C-x C-y

Todo ____ Shift-←→
リンク ___ C-c C-l
書き出し __ C-c C-e
ヘッダ候補 __ CM-i

Luaスクリプト

スクリプトは以下のような感じにした。

emacsMode = hs.hotkey.modal.new()

emacsMode:bind('cmd', '/', 'cheatSheet', function()
    local c = require("hs.canvas")
    sheet = c.new{ x = 100, y = 100, h = 420, w = 900 }:show()
    sheet[1] = { type = "rectangle",
    fillColor = { black = 1.0, alpha = 0.8} }

    sheet[2] = { type = "text", text = '',
    textColor = { white = 1.0 },
    textFont = "Osaka-Mono",
    textSize = 15.0,
    textAlignment = "left",
    frame = { x = "0.0", y = "0.0", h = "1.0", w = "0.33" },
    padding = 5.0, }

    sheet[3] = { type = "text", text = '',
    textColor = { white = 1.0 },
    textFont = "Osaka-Mono",
    textSize = 15.0,
    textAlignment = "left",
    frame = { x = "0.33", y = "0.0", h = "1.0", w = "0.33" },
    padding = 5.0, }

    sheet[4] = { type = "text", text = '',
    textColor = { white = 1.0 },
    textFont = "Osaka-Mono",
    textSize = 15.0,
    textAlignment = "left",
    frame = { x = "0.66", y = "0.0", h = "1.0", w = "0.33" },
    padding = 5.0, }

    local leftText = hs.execute("cat ~/Documents/emacsKey.txt | sed -n 1,18p")
    sheet[2].text = leftText
    local centerText = hs.execute("cat ~/Documents/emacsKey.txt | sed -n 20,27p")
    sheet[3].text = centerText
    local rightText = hs.execute("cat ~/Documents/emacsKey.txt | sed -n 29,44p")
    sheet[4].text = rightText
end, function()
    if sheet then
        sheet:delete()
    end
end)

function appWatch(name, event, app)
    if event == hs.application.watcher.activated then
        if name == 'Emacs' then
            emacsMode:enter()
        else
            emacsMode:exit()
        end
    end
end

appWatcher = hs.application.watcher.new(appWatch)
appWatcher:start()

emacs をアクティブにした状態で[ command ] + [ / ]を押すと、押している間ずっとチートシートを表示し続ける。キーから指を上げると表示が消える。

動作の状況

hs.hotkey.modal

hs.hotkey.modal というモジュールが面白い。
 ドキュメントによると、こんな書きかたが出来るのだ。

hs.hotkey.modal:bind(mods, key, message, pressedfn, releasedfn, repeatfn)
 
 引数に、モディファイキー、組み合わせのキー、メッセージ、キーを押した時の関数、キーを離した時の関数、リピートした時の関数を設定できるのである。
 上記の場合ならモディファイキーは command だ。
 モディファイキーを複数指定する場合はハイフンでつなぐ。例えば :bind('cmd-alt-ctrl' という感じ。

組み合わせるキーは、だいたいキーを押した時に表示される文字でいい。その後のメッセージはなくてもいいが、設定しておくと、キーバインドが押された時に hs.alert.show で表示してくれる。

そのあとの pressedfn で、上記のスクリプトは hs.canvas を使ってチートシートのテキストを表示している。その次の releasedfn で作成した hs.canvas を削除することにした。
 repeatfn は今回必要ないので書いていない。必要ない関数は省いてもいいことになっている。

hs.canvas

pressedfn の部分で hs.canvas「 c 」から、sheet という新規の canvas オブジェクトを作成している。
 canvas オブジェクトは、lua のテーブルみたいな構造だろうと思われる。
 作成には x, y の座標と、h , w という縦横の長さを決めてやる必要がある。

背景となる最初のエレメントを、sheet[1] という形で定義する。このエレメント、というやつもテーブルらしく、各エレメントがレイヤーのように重なっていく。sheet[1] は長方形を意味する rectangle というタイプにして、背景は黒、透明度を 0.8 とした。枠線はデフォルトの 1px の黒である。

次にテキストボックスとなる sheet[2] を定義する。タイプはテキスト、表示する内容はとりあえず空にしておくため text = '' と定義した。他にもフォントやテキストサイズなどを設定した。
 テキストボックスの位置と大きさは frame で設定する。x, y, h, w に入る数値はパーセントである。0.2 なら 20 パーセント、1 で 100 パーセントとなる。
 sheet[2]は左に配置し、同じように作った sheet[3] は真ん中、sheet[4] は右に置くという段組みになっている。

チートシートのテキストは hs.execute でシェルコマンドを実行し、取得している。
 エレメントはテーブルなので、 sheet[2].text = 'テキスト' というふうに代入できる。
 仮に sheet[1] の透明度を、なにかのタイミングで変更するとしたら sheet[1].fillcolor.alpha = 0.3 などと書くことになると思う。

生成した sheet は、キーを離した時に実行される次の function で削除される。
 sheet:delete() というメソッドを使っている。
 
 hs.canvas と hs.hotkey.modal というモジュールの組み合わせは使い出があって、いろいろ面白い。
 hs.console というモジュールで hammerspoon のコンソールを出力できるので、それを表示してみる。

consoleMode = hs.hotkey.modal.new('ctrl', '-')
consoleMode:bind('', 'escape', function()
    consoleMode:exit()
end)

flip = nil
function consoleMode:entered()
    local c = require("hs.canvas")
    flip = c.new{ x = 1100, y = 200, h = 500, w = 320 }:show()
    flip[1] = { type = "rectangle",
    fillColor = { black = 1.0, alpha = 0.8} }

    flip[2] = { type = "text", text = '',
    textColor = { white = 1.0 },
    textFont = "07YasashisaGothic",
    textSize = 14.0,
    textAlignment = "left",
    frame = { x = "0.0", y = "0.0", h = "1.0", w = "1.0" },
    padding = 5.0, }

    local consoleText = hs.console.getConsole()
    local f = io.open(os.getenv("HOME").."/Desktop/output.txt", "w")
    f:write(consoleText)
    f:close()

    local consoleShow = hs.execute("tail -n10 ~/Desktop/output.txt")
    flip[2].text = consoleShow
end
function consoleMode:exited()
    flip:delete(0.5)
end

[ control ] + [ – ]でコンソールの最終 10 行を表示し、[ esc ]で表示を消す。
 コンソールはデスクトップに、テキストファイルにして出力している。

エラーが出ている

なんかチートシートの表示でめっちゃエラー出てる。
 出てるけど、ひとまずは動くから。動きますので。
 なんかでも、これはどうしようもないエラーくさいんだよなぁ。