vc-mode-line

最近知ったのですが, vc-modeが mode-lineに表示している情報(VCS+branch名: gitリポジトリであれば Gitとmasterを表示)はファイルが前のコミットから変更されている場合と されていない場合で異なります. わかりづらいですが, 変更がない場合 VCSとブランチ名のサパレータが -, 変更があった場合は :になります.

変更なし

f:id:syohex:20160210175831p:plain

変更あり

f:id:syohex:20160210175839p:plain

見づらい・わかりづらい

上記で示した通り, 見づらいし, わかりづらいんですが, 有用な情報だなと思ったので, 別情報を表示することにしてみました.

現在のバッファの変更 hunk数の表示

現在お試し中. git-gutterユーザ限定ではあるが, (git-gutter:buffer-hunks)という APIで現在の hunk数を取得している. 変更内容にもよるが, 多すぎるとそろそろコミットしておかないとなぁという気分になれるのでなかなかよい.

f:id:syohex:20160210183017p:plain

(defvar my/vc-mode-line
  '(:propertize
    (:eval (let* ((backend (symbol-name (vc-backend (buffer-file-name))))
                  (branch (substring-no-properties vc-mode (+ (length backend) 2)))
                  (state (if (bound-and-true-p git-gutter-mode)
                             (cl-case (vc-state (buffer-file-name))
                               (edited
                                (format ":%d" (git-gutter:buffer-hunks)))
                               (otherwise ""))
                           "")))
             (concat "(" branch state ")"))))
  "Mode line format for VC Mode.")
(put 'my/vc-mode-line 'risky-local-variable t)

(setq-default mode-line-format
                '("%e"
                  mode-line-front-space
                  mode-line-mule-info
                  mode-line-client
                  mode-line-modified
                  mode-line-remote
                  mode-line-frame-identification
                  mode-line-buffer-identification " " mode-line-position
                  (vc-mode my/vc-mode-line)
                  " "
                  mode-line-modes mode-line-misc-info mode-line-end-spaces))

現在のバッファの変更状態を表示

(git-gutter:statistic) APIを使ってみた場合(上記の設定にはめ込んでみてください). 戻り地は dot-listで carが追加行数, cdrが削除行数となっている. 具体的すぎるように思えるので保留.

f:id:syohex:20160210190348p:plain

Emacs Lispで socketプログラミング

http://mattn.kaoriya.net/software/vim/20160129114716.htm

Emacsが socket使えることは知っていたが, 使ったことがなかったので書いてみました.

コード

(require 'json)

(let ((proc (open-network-stream "test" (get-buffer-create "*test*") "localhost" 8888)))
  (set-process-filter proc (lambda (proc output)
                             (let ((resp (json-read-from-string output)))
                              (message "ただ今: %s" (aref resp 1)))))
  (process-send-string proc (json-encode [1 "GET"]))
  (process-send-eof proc))

open-network-streamは processオブジェクトを返します. 後は start-processみたく扱ってやればよいようです. 非同期です. set-process-filterで callback関数を指定します. process-send-stringでサーバに sendします.

サーバプログラムは上記の記事に載っているものを使っています. 無駄な処理がいくらか含まれてしまっていますが...

結果

サーバを動かし, 上記の式を評価すると以下のようになります.

f:id:syohex:20160131120830g:plain

Emacsで手動スクレイピング

元ネタ

http://qiita.com/muran001/items/7a76a1c70aa12ed68cb6

はてぶのページからリンクを取得する

Vimほど楽ではない気がするが一応

M-x browse-url-emacs [RET] http://b.hatena.ne.jp/ctop/it
;; windowを切り替え, read-only-modeを解除
M-x replace-regexp [RET] >< [RET] >改行< ; 改行は C-r C-jで入力
M-x replace-regexp [RET] ^\s-+ [RET] ; 行頭の空白削除
M-x delete-non-matching-lines [RET] <a [RET]
M-x replace-regexp [RET] ^.*href="\([^"]+\)".*$ [RET] \1

Gifは割愛.

解説

データ取得

M-x browse-url-emacsで直に HTMLファイルを開けます

HTML整形

replace-regexpコマンドで頑張る

M-x replace-regexp [RET] >< [RET] >改行< ; 改行は C-r C-jで入力
M-x replace-regexp [RET] ^\s-+ [RET] ; 行頭の空白削除

フィルタリング

delete-non-matching-linesで指定した正規表現にマッチしない行を削除できる. その逆は delete-matching-linesでマッチした行を残す.

M-x delete-non-matching-lines [RET] <a [RET]

データの整形

やはり replace-regexpで頑張る

M-x replace-regexp [RET] ^.*href="\([^"]+\)".*$ [RET] \1 ; リンクの中身のみ取得

おまけ

正規表現をイメージするときは re-builderも良いかもしれない. 以下は reb-re-syntax'stringのときのスクリーンショットです.

f:id:syohex:20160111215649p:plain

2015年を振り返る

f:id:syohex:20151231214528p:plain

f:id:syohex:20151231214608p:plain

f:id:syohex:20151231214634p:plain

ある日, 古い Emacsパッケージを使おうとしたとき全然動かなくて, 動くための PRを送ったんですが, そのとき他のも動くのかなと古いやつを片っ端からダウンロードし, 試してみたらいろいろ問題が見つかり PRしまくっていました. それで妙に草が生えてしまっています. それに伴い githubリポジトリ数も 1000を超えてしまうということになってしまいました.

あとはひたすらメンテナンスですかね. 今年は新しい物を作らなすぎたと思います. 最後の方は Emacs dynamic moduleのテストのためにあれこれ書いてみましたが, どれも中途半端な感じになってしまいました. 来年は新しいものを作れるようにしたいですね.

f:id:syohex:20151231215225p:plain

f:id:syohex:20151231215308p:plain

自分のやる気のなさが原因としか言えませんが, 早い内になんとかしたいです.

f:id:syohex:20151231215606p:plain

f:id:syohex:20151231215618p:plain

参考

ejectで学ぶ Dynamic module機能

Dynamic module機能は 2015年 12月 12日現在開発中の機能です(だいぶ落ち着いてはきていますが). なので以下の内容は最新版では正しくない可能性があります. 動かない場合等はソースコード及び modules以下に含まれるテストコードを確認してみてください.

リポジトリ

https://github.com/syohex/emacs-eject

リビジョンは 20151212 とします.

ヘッダファイル

関連するヘッダファイルは emacs-module.hだけです. Cファイルではこのヘッダのインクルードが必須です. その他は実装するものに応じて適宜 includeしてください.

emacs-module.hに定義される型で重要になるのが, emacs_envemacs_valueです.

emacs_env

Version 25ではその実体は struct emacs_env_25です. フィールドはほとんど関数ポインタで, Emacs Lisp側から渡された値を Cの型に変換したり, その逆を行うときに必要となります. データのやりとり, 返還等でわからなくなったらとにかくこの構造体のフィールドにそれらしいものがないか確認すると良いでしょう.

C言語側で Emacs Lispから渡された整数値を受け取るときは以下のようになります.

// envは emacs_env*型, eint_valは emacs_value型
intmax_t cval = env->extract_integer(env, eint_val);

逆に C言語側から Emacs Lisp側に整数値を返したい場合は以下のようになります.

// envは emacs_env*型, cint_valは int型.
emacs_value eval = env->make_integer(env, (intmax_t)cint_val);

このように C <=> Emacs Lisp間での値の変換はすべて emacs_env型を使って変換します.

emacs_value

Emacs Lispでの値を表現する型です. Emacs Lisp側から渡ってきた値はすべて emacs_value型です. また C言語から Emacs Lisp側に値を返すときは emacs_value型に変換する必要があります.

コードを見ていく

ライセンスの宣言

https://github.com/syohex/emacs-eject/blob/20151212/eject.c#L28

コードが GPL互換のライセンスでライセンスされていないとリンクできません. なのでそのことを示すためのシンボル plugin_is_GPL_compatibleを宣言します.

追記

GPLでなく, GPL互換のライセンスでした. 失礼しました.

エントリポイント

https://github.com/syohex/emacs-eject/blob/20151212/eject.c#L55-L56

各モジュールでエントリポイントなる関数は emacs_module_initです. ここで C言語の関数の登録, パッケージの登録を行います.

関数の登録

関数の登録の流れは以下のとおりです.

  1. C言語の関数を関数オブジェクト(emacs_value型)に変換
  2. fset関数を呼び出し, Emacs Lisp側で利用する関数名と 1.で作成した関数オブジェクトを関連付ける

それでは順を追って見ていきましょう.

https://github.com/syohex/emacs-eject/blob/20151212/eject.c#L60

emacs_value ejectfn = env->make_function(env, 0, 1, Feject, "Control CD tray", NULL);

make_functionの引数は以下の通りです.

  1. emacs_env*型変数
  2. 引数の最少数
  3. 引数の最大数
  4. C言語関数
  5. docstring
  6. C言語関数に渡す引数

C言語関数のシグネチャ

make_functionに渡せる C言語関数のシグネチャは以下のとおりです.

emacs_value func(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data);

引数はそれぞれ以下のとおりです.

  1. emacs_env*型変数
  2. 引数の個数
  3. 引数
  4. 関数登録時に設定した引数

https://github.com/syohex/emacs-eject/blob/20151212/eject.c#L62-L65

次に作成した関数を登録します. 登録には fset関数を使います. ここで C言語から Emacs Lisp側の関数を呼び出す必要があります. C言語側から Emacs Lisp側の関数を呼び出す方法はひとつだけで, シンボルを internし, 引数を設定, それらを使い funcallを呼び出します.

// (fset 'eject ejectfn)と等価
emacs_value Qfset = env->intern(env, "fset");
emacs_value Qeject = env->intern(env, "eject");
emacs_value fset_args[] = { Qeject, ejectfn };
env->funcall(env, Qfset, 2, fset_args);

これで関数の登録ができました. (eject)と呼び出せば, C言語の関数を呼び出すことができます.

ロードできるようにする

これで関数は呼び出せる準備は整いましたが, ロードできないので provideを呼び出し, requireでロードできるようにします. これも fsetと同じ流れで provide呼び出します.

https://github.com/syohex/emacs-eject/blob/20151212/eject.c#L67-L69

// (provide 'eject) と等価
emacs_value Qprovide = env->intern(env, "provide");
emacs_value provide_args[] = { Qeject }; // 先ほど作成した 'eject
env->funcall(env, Qprovide, 1, provide_args);

これで requireを使ってライブラリをロードできるようになりました.

関数の登録, provideは定形なので, Emacs本体の modules/以下にあるサンプルコードではDEFUNマクロや provide関数が定義されています. (それも面倒なので本体側に何か用意して欲しいところだが...)

eject本体

本体は以下の通りで, 戻り値の部分を除き普通の Cです. 戻り値は emacs_valueである必要があるので, emacs_envの適切な関数フィールドを使い変換します. ここでは成功か失敗かだけわかればいいので, シンボルを返しています.

static emacs_value
Feject(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data)
{
    int fd = open("/dev/cdrom", O_RDONLY|O_NONBLOCK);
    if (fd < 0) {
        perror("open /dev/cdrom");
        return env->intern(env, "nil");
    }

    int cmd = CDROMEJECT;
    if (!env->is_not_nil(env, args[0])) {
        if (ioctl(fd, CDROM_DRIVE_STATUS, 0) == CDS_TRAY_OPEN)
            cmd = CDROMCLOSETRAY;
        else
            cmd = CDROMEJECT;
    }

    if (ioctl(fd, cmd, 0) < 0) {
        perror("ioctl");
        return env->intern(env, "nil");
    }

    return env->intern(env, "t");
}

おわりに

理解を深めるためにもっとコードを書いていこうと思います.