条件分岐 - 環境設定のための Emacs Lisp 入門

Emacs Lisp 入門の第 8 回です。
設定に絞った Emacs Lisp の入門なので、制御構造は必要ないかと思い、前回で終了していました。 しかし、 OS など環境の違う場合に同じ設定ファイルを使おうとすると、条件分岐は必要になってきます。 そこで、今回、 elisp での条件分岐について追加しました。

条件分岐の式

条件分岐の式の基本は if です。
 (if 条件式
     真の式
   偽の式1
   偽の式2
    :)
条件式は真ならば式の 2 番目の引数として渡した式が評価され、 偽ならば 3 番目以降が順に評価されます。


ただし、 if は偽の時は複数書けるのですが、真で評価する式には 1 つしか書けません。 こういった場合、 progn で複数書けます。
 (if 条件式
     (progn 
       式1
       式2
       :))
ただ、ちょっと書くのが面倒なので、真の時だけ渡した式を順に評価する when も用意されています。
 (when 条件式
   式1
   式2
   :)
また、逆に偽の時に実行する unless もあります。

条件式

条件分岐に使う条件式では nil の場合に偽、 t など nil 以外の場合に真として判定されます。
nil 以外ですので、 数値の 0、 空文字などでも 真となります。
 (if nil "true" "false") ;; "false"
 (if t "true" "false")   ;; "true"
 (if 0 "true" "false")   ;; "true"
 (if "" "true" "false")  ;; "true"
空リスト '() が真か偽かは Lisp の処理系によって違うのですが、 elisp では空リストは nil です。
例えば、 変数に 空リスト '() を格納したとしても、その変数には nil が格納されることになります。
 (if '() "true" "false")  ;; "false"
 
 (setq foo '())
 foo ;; nil

一致判定

一致判定には eq を使います。
設定には基本的に eq を覚えておけば十分ですが、 似たような関数として、 equal というのもあり、こちらの方がゆるい一致判定といえます。

関数 機能
eq シンボル、整数値が一致。
equal 2 つの値が一致

 (eq 'foo 'foo)      ;; t
 (eq 1 1)            ;; t
 (eq "foo" "foo")    ;; nil
 (equal "foo" "foo") ;; t

論理式

elisp にも論理式があります。 設定に関して言えば、 not ぐらいを覚えておくぐらいでいいかと思います。

関数 意味 説明
not 否定 真、偽の反転
and 論理積 すべて真の時、真
or 論理和 どれか一つ真の時、真

 (not t)    ;; nil
 (not nil)  ;; t
 
 (and nil t) ;; nil
 (or  nil t) ;; t

設定でよく使う条件式

実際に設定でよく使う条件式を紹介します。

機能 切り替え
system-type OS の判定 OS
window-system Window システムの判定 ターミナルかどうか
file-exists-p ファイルの存在チェック ファイル、ライブラリーの有無
require, load ロードの成功、失敗
fboundp 関数が定義済みかどうかのチェック バージョン

OS

設定ファイルの共通化の基本としては OS で処理を変えることだと思います。
OS を表すシンボルが system-type の変数に格納されており、 これで OS を判断します。

格納されている主なシンボルを以下の表にあげます。 詳しくは C-h v system-type で変数の内容を確認するようにしてください。

シンボル タイプ
gnu/linux GNU/Linux
gnu/kfreebsd Free BSD
darwin Mac OS X
windows-nt Windows
cygwin Windows(Cygwin 版)


なお、 Windows 7 などでも Cygwin でビルドした Emacs でなければ、 windows-nt になります。
また、 シンボル名の '/' の文字はただの文字として扱われています。 elisp は前置法なので、 +, - などわりといろんな記号が変数(シンボル)名に使用できます。
 ;; Windows の場合、 IME の変換でエラーがでないようにする
 (when (eq system-type 'windows-nt)
   (global-set-key [M-kanji] 'ignore)
   (global-set-key [kanji] 'ignore)
   )

ターミナル

window-system の変数には実行時の Window システムが 'x', 'w32' などのシンボルで格納されています。 Window システムというのは、 Unix における X Window System など GUI を実現しているシステムです。 これで、 OS の判定ができなくもないですが、 system-type を使う方が普通です。
では、 window-system を何に使うかというと、ターミナルかどうか判定に使います。 ターミナルではキーコードなどの問題で切り替えが必要なことがあります。
Window システムが使われていないということで、ターミナルでは window-systemnil となっています。
 ;; ターミナルの場合(window-system が nil)、
 ;; <backspace> が C-h になるため、 C-h で一文字削除に設定
 (unless window-system
   (global-set-key "\C-h" 'delete-backward-char)             ;; 全般
   (define-key isearch-mode-map "\C-h" 'isearch-delete-char) ;; インクリメンタルサーチ用
   (add-hook 'c-mode-common-hook
            '(lambda ()
               (local-set-key "\C-h" 'c-electric-delete)))  ;; C 言語系モード
   )

ファイル、ライブラリーの有無

設定ファイルのロード中にエラーが発生すると途中で止まってしまいます。

file-exists-p の関数を使うと、ファイルの有無がチェックできるので、 ファイルがあるときだけロードするということができます。
 (setq custom-file "~/.emacs.d/custom_setttings.el")
 (if (file-exists-p custom-file)
     (load custom-file))
ただ、以前ファイルのロードのところで説明した load第 2 引数に t を指定すると、 「ロード対象のファイルが存在しない」という問題に関しては、エラーを発生しなくなります。
そのため、前述のコードは次のように書けます。
 (setq custom-file "~/.emacs.d/custom_setttings.el")
 (load custom-file t)
load と同様に require第 3 引数に t を指定 すると エラーが発生しなくなります。
これらはエラーは発生しなくても、ロードに成功すれば t 、 失敗すれば nil を返します。 これを利用して、 ロードし、成功した場合だけ実行する処理 が書けます。
 (when (require 'auto-complete-config nil t)
   (ac-config-default))

バージョン

共通して設定ファイルを使用する場合、すべての Emacs が同じバージョンとは限りません。 その対策として、バージョンの番号によって処理を切り替えてもよいのですが、 使いたい関数が定義されているかチェックしてから実行した方が確実です。
fboundp を使うと、関数が定義されているか をチェックすることができます。
 (if (fboundp 'normal-top-level-add-subdirs-to-load-path)
     (normal-top-level-add-subdirs-to-load-path))
以前、変数の解説で、一つのシンボルには 関数と変数が格納(結合)できる と説明しました。
fboundp は関数用ですが、 変数が定義されているかどうかをチェックする boundp という 関数もあります。 ただ、変数の場合は未定義の変数に値を設定したとしても、エラーではないので、 あまり設定では使用しません。



スポンサーサイト



 

自動インデントなど C, C++ 系モード以外でも使えるようになった Emacs 機能の設定

Emacs では C, C++ を始め、 Java 、 JavaScript 、 C# などのモードは cc-mode を元に作られています。 これらの言語はよく使われるせいか cc-mode には自動改行など便利な機能がたくさんあります。
そういった機能を使うのに慣れていると他の言語の編集中にめんどくさいなと思うことがよくあります。 そう思う人は多いようで、 Emacs のバージョンがあがって、他の言語モードでも使えるように格上げ(?) された機能があります。
今回はそんな C 系モード以外でも使えるようになった機能のいくつかを設定方法を含めて紹介したいと思います。

紹介する機能はモード行で "/lah" 表示されている 3 つとタブを押した時の挙動の設定です。
機能 機能名
がっつり削除 hungry-delete
自動インデント electric-indent, c-elecric-state
自動改行 electric-layout, c-auto-newline
タブキーの挙動の変更 tab-always-indent


なお、これらの機能は基本的に C 系モードではそちらの設定が使われます。 そのため、設定方法については一般、 C 系モードの両方について説明しています。

変数設定の注意点

各機能の設定方法の前に注意点をいくつか説明しておきます。

バッファーローカル変数

変数には Emacs 全体で同じ値を使う通常の変数とバッファー毎に違った値を保持できる バッファーローカル変数があります。
バッファーローカルな変数は setq などでそのまま設定しても意味がありません。 ホックを使って、バッファーを開いた時に設定する必要があります。

C 系モードすべて
 (defun my-all-cc-mode-init ()
   ;; C 系(cc-mode を継承した)モード共通の設定を記述
 
   (setq tab-width 8)
   )
 (add-hook 'c-mode-common-hook 'my-all-cc-mode-init)
C, C++ モード
(defun my-c-c++-mode-init ()
  ;; C, C++ 用の設定を記述  

  (setq tab-width 4)
  )
(add-hook 'c-mode-hook 'my-c-c++-mode-init)
(add-hook 'c++-mode-hook 'my-c-c++-mode-init)
バッファーローカル変数に対して、 Emacs で共通した値を設定することもできます。 正確にいうとバッファーローカル変数のデフォルト値を指定する感じです。
それには次のカスタマイズで設定するか、 setq ではなく setq-default などで設定します。
(setq-default tab-width 8)

カスタマイズ

Emacs では GUI 風に設定が可能なカスタマイズという機能があります。
これを使用する場合、 M-x customize-option の後に設定したい変数名を指定して起動します。 なお、前節でも触れたようにカスタマイズで設定した値はバッファーローカルにならないということも注意する必要があります。

トグル系の有効/無効の指定

変数で機能の ON/OFF を設定する場合、通常 t, nil を使います。
これがモードの有効/ 無効など関数で指定する場合、 1, 0 を使って設定します。 1 と t はどちらでもよいのですが、 nil を指定すると、 ON/OFF の切り替えになるため、 OFF にするには 0 を指定します。
 (c-toggle-hungry-state 1) ;; 有効
 (c-toggle-hungry-state 0) ;; 無効

がっつり削除 (hungry-delete)

まずは分かりやすい機能から説明します。
コーディングの場合、インデントや空行などを大量に使います。 がっつり削除(hungry-delete) の機能を使うと [Back Space][Delete](C-d) で スペース、タブ、改行の空白文字をまとめて一気に削除します。

emacs_hungry_delete.png


この機能に慣れてしまうと、他の言語での削除がかなりタルく感じます。実際、私は我慢できず自分で削除用の関数を作って使っていました。
今ではそんなことをしなくても、一行設定を書くだけでどのモードでも使えるようになっています。

この機能を有効にする場合、バッファーローカルな機能なので、各モードのホックで (hungrry-delete-mode 1) の記述を行います。
ただ、できて困るものでもないですし、全モードで有効にした方が簡単です。そういった場合、 init.el に以下の記述を行います。
(global-hungry-delete-mode 1)
ただし、 C 系モードの場合、そちらの設定が使われるため、別途設定する必要があります。 こちらは C 系共通のホックなどに以下の記述を行います。
(c-toggle-hungry-state 1)
C 系モードではこの hungry-delete 機能が有効になっているとモード行に "/h" が表示されます。

C 系モードでの自動インデント、自動改行

C 系モードでの自動インデント、自動改行の機能について説明します。 この 2 つは設定変更時に連動することが多く、セットで使うと覚えておいた方が簡単です。

自動インデント(c-electric-state)は、改行や { などのキーを入力した時に インデントも自動で行う機能です。
有効時には "/l" が付きます。

自動改行(c-auto-newline)は、 {; などのキーを入力した時に 自動で改行を行う機能です。
有効時には "/a" が付きます。

emacs_electric_state.png


有効にする場合は C 系共通のホックなどで以下を記述します。
(c-toggle-auto-newline 1)
これらの機能は非常に便利です。 ただ、有効に利用するにはスタイル、インデント量などをちゃんと設定しておく必要があります。

自動インデント (electric-indent-mode)

C 系モードの自動インデントの機能は 24.4 のバージョンで Emacs 全体で使えるようになり、 しかもデフォルトで有効になっています。 ただ、これがかなりタチが悪いです。

機能は C 系と同じような感じで改行や特定の文字(electric-indent-chars で指定)で自動でインデントします。
問題なのは C-j[Return](C-m) が入れ替えられるということです。 もともと C-j に "改行してインデント" という機能が割り当てられたいるのですが、これを入れ替えることによって 改行時のインデントが実現されています。

C-j での改行に慣れている人間にとっては、これがめちゃくちゃ邪魔で、「何勝手なことしてるんだ」って気がします。 ということで、この機能を無効にします。
無効にする場合には以下の記述を init.el に追加します。 この機能はバッファーローカルではないので、ホックで記述する必要もありません。
(electric-indent-mode 0)
ただ、今まで "リターンで改行して、タブでインデント" とやっていた人にとっては 便利だと思うので、そのままでもいいかもしれません。

自動改行 (electric-layout-mode)

Emacs 全体での自動改行の機能は electric-layout-mode です。 こちらは特定の文字を入力すると自動で改行を行います。

これを有効にする場合は以下の記述をします。こちらもバッファーローカルではありません。
(electric-layout-mode 1)
しかし、これは設定してもそのままでは特に何もかわりません。 というのも改行を挿入する文字は electric-layout-rules で設定するのですが、 ここに何も設定されていないからです。
こういうは言語のモード作成者側で設定するようなものだと思うので、 これからの機能ではないかと思います。



とはいえ、一応、設定方法も紹介しておきます。

electric-layout-rules は (対象文字 . 改行位置) のコンスセルのリストで設定します。 改行位置は befor(前)、 after(後)、 around(前後) などで指定します。

C 系の言語以外だとあまりこの機能が役立つものを思いつかないのですが、 唯一使っている CMake モードの設定を例としてあげます。
以下の記述で ")" の文字を入力すると改行するようになります。
 (add-hook 'cmake-mode-hook '(lambda ()
                              (setq-local electric-layout-rules '((?\) . after)))
                              ))
ここで注意点ですが、 electric-layout-rules はバッファーローカルではありません。
そのため、ホックに書いたからといって普通に setq で設定すると、全モードで ")" で改行するようになります。 それだと問題なので、 setq-local を使って、バッファーローカルにして値を設定しています。


この手の変数は明らかにバッファーローカルにしておくべきです。 しかし、"モード毎に設定が必要な変数" に対する Emacs の方針が、"バッファーローカルな変数を必要な場合に setq-defalut などで設定する" から "通常の変数としておいて setq-local で設定する" というように変わってきたのかもしれません。
ただ、自動インデントのことも考えると electric.el を書いた人が「単にどうかしている」という可能性も捨てきれません。

[Tab] キーの挙動の変更 (tab-always-indent)

Emacs では基本 [Tab] キーはインデントで、 本当にタブを挿入したい場合、 C-q [Tab]M-i を使います。
これだとタブ挿入したい場合にめんどくさいのですが、 tab-always-indent の設定を変えると、 挙動を変えることができます。

動作
t 常にインデント (デフォルト)
nil 左端(文字の前)ではインデント、それ以外はタブの挿入
t, nil 以外 コメント、文字列などではタブ、それ以外はインデント

nil を設定しておくのが一番使いやすいのではないかと思います。

emacs_tab_always_indent.png


こちらも C 系モード用の両方を設定する必要があります。
 (setq tab-always-indent nil)
 (setq c-tab-always-indent nil)
これらの変数はバッファーローカルではないため、そのまま設定ファイルに記述できます。

設定の記述

最後に今までの項目をまとめた設定を記述しておきます。
自動改行はスタイルなどをちゃんと設定していないと逆に邪魔なのでコメントアウトしていますが、 設定をして、有効に変えた方がよいです。
 ;; がっつり削除
 (global-hungry-delete-mode 1)

 ;; 改行時などの自動インデント(C-j と C-m の入れ替え)を禁止
 (electric-indent-mode 0)
 
 ;; 特定の文字を入力すると自動で改行
 ;; (electric-layout-mode 1)
 
 ;; 左端(文字の前)ではインデント、それ以外はタブの挿入
 (setq tab-always-indent nil)
 (setq c-tab-always-indent nil)
 
 
 
 ;; C 系(cc-mode を継承した)モード共通の設定を記述
 (defun my-all-cc-mode-init ()
 
   ;; がっつり削除
   (c-toggle-hungry-state 1)
 
   ;; ";", "}" などを入力したときに自動改行
   ;; 自動インデントも一緒に ON になる
   ;; (c-toggle-auto-newline 1)
 
   )
 (add-hook 'c-mode-common-hook 'my-all-cc-mode-init)
 
 
 ;;  C, C++ モードのみで有効にしたい場合はこちらに記述
 (defun my-c-c++-mode-init ()
   ;; C, C++ 用の設定を記述  
 
   )
 (add-hook 'c-mode-hook 'my-c-c++-mode-init)
 (add-hook 'c++-mode-hook 'my-c-c++-mode-init)
 
このページをシェア
アクセスカウンター
アクセスランキング
[ジャンルランキング]
コンピュータ
26位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
8位
アクセスランキングを見る>>
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

05月 | 2016年06月 | 07月
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 - -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。