12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

簡単なEmacs拡張を作ってみたよ

Posted at

この記事はACCESS Advent Calendar 17日目の記事です。
こんにちは!無人島にEmacs拡張一つ持っていけるとしたらelscreenを持っていくEmacsライトユーザの@kumabookです。
Emacsは自分でカスタマイズできることが大きな特徴・メリットですが、今まで自分は他の方が作ってくれている拡張を入れて少し設定ファイルを書くぐらいで自分で拡張を作ったことはありませんでした。
今回は脱ライトユーザすべく簡単なEmacs拡張を作ってみたいと思います。

拡張の作り方を学ぶのが目的なので、あまり機能に悩まないものにしたいと思います。以前LGTM.inから画像を取ってくるというFirefox拡張を作ったので、それのEmacs版を作ってみたいと思います。

Caskを使った開発環境の構築

プロジェクトの作成

Caskはパッケージ管理をしてくれるものですが、実は開発環境の構築も可能みたいです。

mkdir lgtm-emacs
cd lgtm-emacs
cask init --dev

とすると開発用のCaskファイルが生成されます。

Cask
(source gnu)
(source melpa)

(package-file "TODO")

(development
 (depends-on "f")
 (depends-on "ecukes")
 (depends-on "ert-runner")
 (depends-on "el-mock"))

TODOとなっているところに拡張のメインのelisp のファイル名に変更します。今回は lgtm.elにします。
依存ライブラリなどを指定することができますが、それは後述します。


次に、lgtm.elを作成していくわけですが、拡張用のelispはお作法に沿って書く必要があります。
具体的にはファイルの先頭にコードの情報をコメントとして書く必要があります。これを書かないとcaskコマンドでエラーになってしまいます。自分はこれで少しハマってしまいました。
詳しくは こちらを参照してください。

lgtm.el
;;; lgtm.el --- lgtm.in client

;; Copyright (C) 2015 Hiroki Kumamoto

;; Author: Hiroki Kumamoto <kumabook@live.jp>
;; Version: 0.1
;; Package-Requires: ((request "1.0"))
;; Keywords: multimedia, frobnicate
;; URL: https://github.com/kumabook/lgtm-emacs

;;; Commentary:

;; Show a LGTM image from lgtm.in at random

;;;###autoload



重要なのはPackage-Requiresの部分です。
ここの部分に依存する別のパッケージを書く必要あります。

ライブラリの利用

拡張はelispを使って書くのですが、すべての機能を自分で書く必要はなく他の人が作ったelisp利用することができます。
またその依存を宣言しておいて、依存するライブラリも一緒にインストールされるようにすることができます。
依存は先述したPackage-Requiresに書きます。合わせて、Caskを使った開発の際にはCaskファイルにも依存を書きます。

今回は、http requestをいい感じにかけるrequest.elというライブラリを利用したいと思います。
ですので、Caskファイルに以下を加えます。

(depends-on "request")

以下のコマンドでプロジェクトの.cask以下にライブラリがダウンロードされます。

cask install

Elispを書く

まずはHello world

準備ができたので、まずはhello worldしてみたいと思います。

lgtm.el
(message "Hello World. LGTM!")

messageは*Message*にprintする関数です。

cask execコマンドで.cask以下をload-pathに追加した状態でemacsを実行できます。デフォルトだとcuiのemacsが立ち上がるので、今回はGUIのEmacs.appを指定しています。また、-l で読み込むファイルを指定できるので、lgtm.elを指定しています。

cask exec ~/Applications/Emacs.app/Contents/MacOS/Emacs -l lgtm.el

開発用のEmacsが立ち上がって*Message*を開くと
メッセージが出力されていると思います。

コマンド化

lgtm.el
(defun lgtm ()
  "Show a LGTM image from lgtm.in at random"
  (interactive)
  (message "Hello World. LGTM!"))

defunで関数を定義できます。関数の先頭でinteractiveを呼ぶと
M-xから呼べるコマンドになります。

request.elを使ったJSON APIアクセス

lgtm.inはJSON APIを提供してくれています。
これを叩けばLGTM画像のURLとmarkdown用のテキストが入ったJSONが取得できます。
また、request.elを使えばjQuery.getJSON のように簡単にJSON APIを叩けます。

lgtm.el
(defun lgtm ()
  "Show a LGTM image from lgtm.in at random"
  (interactive)
  (request
   "http://www.lgtm.in/g"
   :type "GET"
   :params '()
   :parser 'json-read
   :headers '(("Accept" . "application/json"))
   :success (function*
             (lambda (&key data &allow-other-keys)
               (message (assoc-default 'actualImageUrl data))
               (message (assoc-default 'markdown data))))))

テンポラリバッファへの画像・markdownテキストの表示

画像のURLとmarkdownテキストが取得できたので、これをテンポラリバッファに表示するようにしたいと思います。

URLの画像を表示するにはダウンロードしてその結果からcreate-imageする必要があるようです。 参考URL

lgtm.el
(defun insert-image-from-url (url)
  (let ((buffer (url-retrieve-synchronously url)))
    (unwind-protect
        (let ((data (with-current-buffer buffer
                      (goto-char (point-min))
                      (search-forward "\n\n")
                      (buffer-substring (point) (point-max)))))
          (insert-image (create-image data nil t)))
      (kill-buffer buffer))))

このままだと現在開いているバッファに出力されてしまうので、*lgtm*という名前でテンポラリバッファを作ってそこに出力したいと思います。
テンプラリバッファはget-buffer-createで作れるようです。
今回はコマンドが叩かれるたびに画像を差し替えたいので、
すでにbufferが存在すればkill-bufferするようにしたいと思います。またswitch-bufferで作成したバッファへ切り替えることができるようです。

lgtm.el
(kill-buffer (get-buffer-create "*lgtm*"))
(switch-to-buffer (get-buffer-create "*lgtm*"))
(insert-image-from-url (assoc-default 'actualImageUrl data))
(insert "\n")
(insert (assoc-default 'markdown data)

これで、M-x lgtmと打てばLGTM画像とマークダウンテキストが表示されるようになりました。
lgtm-emacs.png

まとめ

今回は、簡単なJSON APIを叩くEmacs拡張を作ってみました。

  • Caskを使った開発環境の構築
  • request.elを使ったJSON APIクライアントの作成
  • テンポラリバッファへの画像・markdownテキストの表示

ライブラリを利用できるので、思ったよりも簡単に拡張が書けることがわかりました。
また、今回は試しませんでしたが、テスト周りも充実していて

  • unittest, mock
  • evmを使った複数バージョンのEmacsのテスト
  • Travis CI

といったものは今後試してみたいです。

画像の読み込みが同期的になってしまっています。UIをブロックしないように並列・非同期にしたいのですが、Emacs本体はマルチスレッドに対応しておらず、外部プログラムを駆使しなければならないようで、中々大変そうです。良さそうなライブラリもありそうなので、うまく利用できれば、画像をもう少しまとまった数同時に読み込むことも実装できそうです。

最後に

今回作ったものは簡単なものでしたが、
普段ブラックボックスとして利用しているものの中身を勉強して、拡張してみるというのは、自分の手の届く範囲が広がった感覚がありとても新鮮でした。まずは、lisp力をもっと上げたいですね。

ともあれEmacsはサイコーです。

今回作ったものはこちらになります。

では C-x C-c!

--
この記事はACCESS Advent Calendar 17日目の記事です
明日は @pankona さんがgo mobileの話をしてくれます。

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?