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");
}

おわりに

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

Suppress warning of exec-path-from-shell

Sorry I removed old entry about this thing by mistake. This entry is updated version of it.

Add following configuration in your init.el. Then exec-path-from-shell does not warn about environment variables.

(custom-set-variables
 '(exec-path-from-shell-check-startup-files nil))

Recently exec-path-from-shell warn if environment variable in interactive mode is different from them in non-interactive mode. I suppose this is right. We should set environment variables in .bash_profile or .zshenv, not .bashrc, .zshrc. However this is not easy for some configurations such as rbenv. rbenv initialization eval 'rbenv init -' generates some environment variables setting and function definitions. We should not define functions in .bash_profile, .zsh_profile so I think such setting should not be moved. And I want to keep shell configuration in only one file :-)

color-theme-modern

I ported all color-theme themes(over 100 themes) to Emacs theme framework 2 years ago. Now those themes are available on MELPA. You can download themes with package.el. Package name is color-theme-modern.

Repository

All sreenshots are here

https://github.com/emacs-jp/replace-colorthemes

Installation

All themes are available on MELPA and MELPA stable

You can install color-theme-modern with the following command.

M-x package-install color-theme-modern

Enjoy Emacs themes.

Indent like python-mode in coffee-mode

f:id:syohex:20151010173847g:plain

https://github.com/defunkt/coffee-mode/pull/323

I have implemented indentation like python-mode in coffee-mode. You can enable this feature by setting coffee-indent-like-python-mode to non-nil.

(custom-set-variables
 '(coffee-indent-like-python-mode t))

If you don't like default coffee-mode indent-command, please try this. And I suppose this is useful for Evil users. o and O commands works well as you expect with this indentation mode.

migemo対応の helm sourceを書く(helm-migemo不要)

昔 helmは migemo対応していたのですが(anything時代から), それが除去され, migemo機能を有効にする helm-migemoというパッケージが作成されさました. しかし最近 helmが再度 migemo対応しました. これにより helmだけで migemo対応の sourceが書けるようになりました. その手順を示します(作者の方が主導的に行われたので再度外される可能性はないとはいえませんが, 高くはないと思います).

バージョン

1.7.9以上を使う必要があります.

事前準備

migemoの設定を行い(以下は cmigemo), helm-modeを有効にします. (NOTE helm-migemo-modeは autoloadできないので, 事前に helmをロードしています)

(require 'migemo)
(setq migemo-command "migemo")
(setq migemo-options '("-q" "--emacs"))

;; Set your installed path
(setq migemo-dictionary
      (expand-file-name "/usr/local/share/migemo/utf-8/migemo-dict"))

(setq migemo-user-dictionary nil)
(setq migemo-regex-dictionary nil)
(setq migemo-coding-system 'utf-8-unix)
(migemo-init)

(require 'helm)
(helm-migemo-mode +1)

サンプル

source作成マクロで migemo属性を non-nilに設定します.

(defvar sample-source
  (helm-build-sync-source "Sample helm-migemo"
    :candidates '("りんご" "みかん" "メロン")
    :action 'message
    :migemo t))

(defun sample-helm-migemo ()
  (interactive)
  (helm :sources '(sample-source) :buffer "*helm-sample*"))

イメージ

上記の実行結果(M-x sample-helm-migemo)は以下のようになります.

f:id:syohex:20151010171556g:plain

Delete line in edit mode

f:id:syohex:20151010161719g:plain

I have implemented delete line feature in helm-ag edit mode. We can put delete line mark by C-c C-d, and remove delete mark by C-c C-u by default. Marking some lines and commit(C-c C-c by default), then these lines are deleted.

Please report me if you have issues or suggestions via github issues

hcl-mode 0.01 released

hcl-mode 0.01 is out. hcl-mode provides major mode of HCL(Hashicorp Configuration Language). Currently hcl-mode provide indentation, highlighting, some cursor moving commands. If you have any issues or suggestions, please report me via github issues, I'll try to implement/fix as possible.

Repository

https://github.com/syohex/emacs-hcl-mode

Installation

You can install hcl-mode from MELPA with package.el.

Screenshot

f:id:syohex:20151006082112p:plain