ことさら−古都プログラマーの更級日記

京都でお寺を回りながら御朱印集めをしていたり、LoLをしたり試合を見に行ったりしているエンジニアのブログです。技術的なはなしとか日常的なはなし、カメラやLoLや競馬の話も書きます。右メニューに検索やらカテゴリーやらがあるので、見たい記事だけ見てね!

Emacsでマイナーモードを定義してみたり、 M-x で動くインタラクティブ関数を定義してみたりする

f:id:yoshiki_utakata:20171218123257j:plain

はじめに

この記事は Emacs Advent Calendar 2017 - Qiita の18日目の記事です。

パッケージをインストールしているだけでは物足りない時がある

EmacsPHPシンタックスハイライトやコードフォーマッタを導入する、といった場合には、適当なパッケージを導入すればすみます。しかし、実際現場で開発をしていると、パッケージの導入だけでは物足りないこともあります。そんな時にカスタマイズできるのがEmacsの良さであります。

とは言え、パッケージを自作するというのはハードルが高いので、ここでは以下の2つを紹介します。

  • interactive 関数を定義してで簡単な処理をEmacsから実行できるようにする
  • Easy-Mmode で簡単なマイナーモードを定義する

interactive 関数を定義して簡単な処理をEmacsから実行できるようにする

簡単にできて地味に便利です。実際に僕が定義している関数を紹介します。その前に、念のためelispでの関数定義の方法について書きます。

vagrantをreloadする関数を定義する

僕は以下のような関数を定義しています。

(defun local-vagrant-reload()
  (interactive)
  (async-shell-command "cd ~/vagrant && vagrant reload"))

これにより、EmacsM-x local-vagrant-reload すると、vagrant reload してくれます。

軽く説明します。

  • (defun local-vagrant-reload() ... ) これで local-vagrant-reload という関数を定義します。
  • (interactive) これがあると、 M-x <関数名> で呼べるようになります。
  • (async-shell-command ...) 関数本体です。シェルのコマンドを実行するだけです。

シェルのコマンドを関数として定義するのは結構便利です。

logをtailする関数を定義する

以下のような関数を定義しています。

(defun local-vagrant-log-tail(logname)
  (interactive "sLogName:")
  (async-shell-command
   (concat"ssh vagrant@local-vagrant 'tail -f /var/log/" logname ".log'")))

これを実行するにはこうします。

  • M-x local-vagrant-log-tail を実行
  • LogName: と表示されるので、 apache/access_log などと入力
  • local-vagrant上の /var/log/apache/access_log を tail した結果が表示されます

先程のよりちょっとパワーアップしました。関数が引数を取っている点が大きな違いです。

  • (defun local-vagrant-log-tail(logname) ... ) logname という引数を取る関数の定義です。
  • (interactive "sLogName:") 後で詳しく説明します。
  • (async-shell-command (concat"ssh vagrant@local-vagrant 'tail -f /var/log/" logname ".log'")) 先程と同様にシェルのコマンドを実行します。concat は文字列を結合する関数です。

(interactive "sLogName:") について

(interactive "sLogName:") は、 M-x <関数名> で関数を呼べるようにして、かつミニバッファで引数を受けられるようにするための一文です。

sLogName: という部分がキモになります。これは、一文字目の sLogName: に分かれます。 LogName: の方は、引数を受け取る時にバッファに表示する文字列です。 s の方は、引数が文字列(string)である事を表しています。

s の変わりに n を入れると、数値を受け取れます。例えば nCount: と書くと、 Count: をいう文字列をバッファに表示しつつ、数値の引数を受け取れます。

s , n 以外にも色々ありますが、大体この2があれば簡単な処理は足りると思いますのでここでは割愛します。

これだけできれば簡単なことは大体Emacsからできるようになりますし、Emacsで開発している人はちょっと便利になるでしょう。

Easy-Mmode で簡単なマイナーモードを定義する

業務でコーディングしていると、複数のプロジェクトに関わることがあります。同じ言語でも異なるコーディング規約を採用している場合もあるでしょう。JavaScriptだけど、こっちはインデントが2で、こっちは4。あるいは、インデントがタブの所もあれば4スペースのところもある...などなど。プロジェクトによってコーディング規約を変更したい場合はマイナーモードを定義すると便利です。そして、マイナーモードの定義には Easy-Mmode が便利です。

Easy-Mmode でマイナーモードを定義する

インデントをtabに変更するモードを定義してみます。

(easy-mmode-define-minor-mode
 tab-mode ;モード名
 "Indent tab mode." ;モードの説明
 nil ;初期値がONかどうか
 " Tab" ;モードラインに表示される文字列
 nil) ; キーバインドの設定

;; hookを登録
(add-hook
 'tab-mode-hook
 '(lambda ()
    (setq indent-tabs-mode t) ;インデントにはタブを使用
    (setq c-basic-offset 4) ;タブ幅を4に設定
    ))

Easy-Mmode で tab-mode というモードを定義したうえで、そのhook(tab-modeに切り替わった時に呼ばれる関数)を設定、中では intent-tabs-modet に設定して、インデントにタブを仕様するようにしています。

この定義だと、 tab-mode を抜けた時に、インデントがスペースに戻らないので、きちっと定義するのであればもう少し工夫が必要ですが、個人で使う時には十分です。

インデントがtabのプロジェクトを開いた時は、 M-x tab-mode を手動で実行します。ここも自動でやりたいのですが、面倒なので一旦このような運用をしています。

まとめ

パッケージだけではなかなか設定しづらいこまかいところを、独自のelispで解決する話でした。みなさんもelispを書いて開発しやすいエディタにしていきましょう。