Emacs-Lisp入門 2021
*scratch* バッファ
S式の評価
- 直前のS式を評価 →
C-j
(結果は次行) - カーソルの直前のS式を評価 →
C-x C-e
/eval-last-sexp
(結果はミニバッファ) - カーソルの位置か、そこ以前の行頭で始まるS式を評価 →
M-C-x
/eval-defun
- バッファ全体を評価 →
M-x eval-buffer
- 選択範囲を評価 →
M-x eval-region
各種定義の確認
-
関数定義の確認 →
(symbol-function '関数名)
-
interactive
じゃない関数をEval →M-:
/eval-expression
-
入力履歴は
M-:
からのM-p
/M-n
で辿れる-
C-x M-:
/C-x M-ESC
で直前のコマンドを再実行 /repeat-complex-command
-
-
入力シンボルの補完 →
M-Tab
(emacs-list-mode
) -
関数(名)の検索 →
M-x apropos
/ ドキュメントも含むapropos-documentation
/C-h f 関数名
-
関数のマニュアル表示 →
M-x describe-function
-
定義ジャンプ →
M-x find-function
- 定義ジャンプ(
xref
) →M-x xref-find-definitions
/M-.
- 定義ジャンプ(
-
キーバインドされた関数名の確認 →
M-x describe-key
/C-h k
-
関数にバインドされたキーの確認 →
M-x where-is
/C-h w
-
現在のキーバインド全体の確認 →
M-x describe-bindings
/C-h b
新規バッファ → C-x b バッファ名
特定条件でループを抜ける → catch~throw
-
while (true) { if(cond) break; ..}
のような動作
(catch 'found
(while foo-list
... 何か処理
(if (condition)
(throw 'found t) ; found を t で返す
...)))
- コンスセル
; 以下は等価
(+ 1 2)
(+ . (1 . (2 . nil))
- おまけ →
(zone)
メジャーモード
メジャーモードとファイル名の関連付け変数 → auto-mode-alist
メジャーモードの条件
- モード名
-
(setq major-mode 'my-mode)
(モード名) -
(setq mode-name "My Mode")
(モードラインに表示される名前)
-
- キーマップ(ローカルマップ)の設定
- モード用のローカルキーマップ変数の定義
-
(setq my-local-map (make-sparse-keymap))
-
make-sparse-keymap
→ 機能未割当の空のキーマップを生成する関数
-
-
- キー割り当て
(define-key my-local-map "h" 'backword-char)
(define-key my-local-map "j" 'previous-line)
- ローカルマップを有効にする
(use-local-map my-local-map
- モード用のローカルキーマップ変数の定義
- 必要な変数定義
- TODO...
上記をまとめると以下がメジャーモードの最低限のひな型
(defun my-mode ()
(interactive)
(setq major-mode 'my-mode
mode-name "My Mode")
(setq my-local-map (make-sparse-keymap))
(define-key my-local-map "h" 'backword-char)
(define-key my-local-map "j" 'next-line)
(define-key my-local-map "k" 'previous-char)
(define-key my-local-map "l" 'forward-char)
(use-local-map my-local-map))
メジャーモードの作成例
関数まわり
基本
(defun 関数名 (引数)
"説明文"
定義...)
オプション引数 → (a &optional foo bar)
/ 省略時は nil
可変長引数 → (a &rest b)
/ (1 2 3)
のとき a=1
, b=(2 3)
インタラクティブ関数
-
C-n
やM-x info
のようにキーボード操作で読み出せる関数 - 関数定義に
(interactive)
が必須- 引数(省略可能)は対話的呼び出し時に引数をどう受け取るか、プロンプトの指定
-
s
から開始すると文字列を受け取る、という変わった仕様 -
\n
が引数の区切り-
sHogeFuga\nnFooBar
なら最初の引数は文字列、次が数値
-
(defun interactive-function (a b)
(interactive "sInput string a:\nsInput string b:")
(message (concat a b)))
ラムダ式
(lambda (args) 定義...)
fset
を使うと
(defun plus1 (x) "add 1" (1+ x))
; 以下と等価
(fset 'plus1 '(lambda (x) "add 1" (1+ x)))
#'
)
シャープクオート(以下の回答が詳しい
とりあえずは以下の通り
- 関数シンボルをクオートしたいときは
#'
でクオートしておけばよい- クオートしているものが関数シンボルだと明示することもできるかな
-
(let ((...)) #'(lambda (arg)))
のようなクロージャを作りたいときは#'
apply と funcall の違い
以下の記事が詳しい
変数
- 変数はシンボル(
'foo
)として存在-
foo
のように quote なしの場合、参照先の値を取得する-
'foo
→int* pointer
(C言語でいうとポインタ変数) -
foo
→*pointer
(C言語でいうとアドレスの指す先の値)
-
-
名前空間
- Emacs-Lisp の名前空間は一つのみ!
- C言語と同じ
- Prefixを付けて変数名の衝突に気を付けること
- C言語と同じ
動的スコープ
実行時の設定が事前定義された変数の値を書き換える。
(setq x 5)
(defun subfunc ()
(insert (format "x is [%d] in subfunc\n" x)))
(defun main ()
(let ((x 3))
(insert (format "x is [%d] in main\n" x))
(subfunc)))
(main)
;-> x is [3] in main
;-> x is [3] in subfunc (xの値が5から3に代わっている)
定義
-
グローバル変数 →
(defvar foo 初期値 "説明文")
- 初期値が代入されるのは未定義変数のときのみ
代入(束縛)
- 確保したメモリ領域に値をセット(代入)するのではなく、シンボルに対して値を紐づけるので束縛。
(set 'foo 5)
; もしくは
(setq foo 5) ; 糖衣構文のようなもの(マクロ)
は
int *pointer;
*pointer = 5;
局所変数(let, let*)
-
(let ...)
の中だけで有効な変数
(let ((var1 "val") var2)
(message var1 var2))
var1="val"
、var2=nil
-
let*
だと束縛後からすぐ参照可能になる
(let* ((foo 3) (bar foo))
bar)
foo=bar=3
編集関数
ポイント(カーソル位置)
バッファの先頭か? → (bobp)
バッファの末尾か? → (eobp)
行頭か? → (bolp)
行末か? → (eolp)
n文字進む/戻る → (forward-char n)
/ (backward-char n)
1行進める → (forward-line)
/ elisp なら next-line
よりこちらの関数を使う
n行へ移動する → (goto-line n)
行頭/行末へ移動 → (beginning-of-line)
/ (end-of-line)
S式単位で進む/戻る → (forward-sexp)
/ (backward-sexp)
現在のポイント位置の取得 → (point)
ポイントの開始と末尾の位置 → (point-min)
/ (point-max)
選択範囲の取得 → (region-biginning)
/ (region-end)
指定位置へ移動 → (goto-char p)
/ (goto-char (point-end))
とかする
現在位置を保存して処理後に戻る → save-excursion
; ただしバッファ切り替えの場合は戻らない
(save-excursion
(goto-char (point-min))
(insert "foo"))
ウィンドウに表示されている領域でのn行名へ移動する → (move-to-window-line n)
- バッファ全体ではなく現在表示されている画面で何行目か?ということ
-
(move-to-window-line 0)
なら表示画面内の先頭行へ移動する。
行頭からn文字目へ移動 → (move-to-column n &optional 強制フラグ)
- 強制フラグが
non-nil
のとき行末を超えたときに空白文字が追加されて指定桁またで移動する
現在の桁位置 →(current-column)
Emacs の文字列操作では、C言語のファイルポインタの read
/seek
にあたるものが ポイント(カーソル)になる。
よって、以下のような思考フローとなる
- 編集したい位置へポイントを移動する(ポイント移動関数)
- バッファ全体なら
(point-min)
などバッファ先頭へ移動する
- バッファ全体なら
- 編集したいものを探す
-
search-*
/re-search-*
などの検索系で編集したい位置のポイントを探す
-
- 任意のコードで編集処理を行う
- この時に
save-excursion
などで編集前後の状態を保存したり戻したりする
- この時に
カーソルを移動させるので、後処理をしっかししないと関数実行後に画面位置が知らない場所に移動したりするので注意。
Emacs-Lisp講座のサンプルプログラムを例に
(defun my-caclurator ()
(interactive)
(save-excursion ; カーソル位置の保存(後処理)
(let (col (sum 0) n) ; 局所変数の準備
(goto-char (point-min)) ; カーソル位置を先頭に戻す(バッファ全体が対象)
(re-search-forward "^--BEGIN$") ; 処理したい位置まで移動する(ファイルポインタの seek にあたる)
(while (re-search-forward "\\(^--END\\)\\|\\([0-9]+$\\)" nil t) ; 処理したい対象を正規表現で検索
(cond ; 見つかった行ごとに分岐して処理
((match-beginning 2) ; `[0-9]+$` の場合
(setq n (match-string 2))
(setq n (string-to-number n))
(goto-char (match-beginning 2))
(skip-chars-backward " \t")
(delete-region
(point)
(progn (end-of-line) (point)))
(move-to-column 70 t)
(insert (format "%5d" n))
(setq sum (+ sum n)))
((match-beginning 1) ; `^--END$` の場合
(delete-region
(point)
(progn (end-of-line) (point)))
(move-to-column 70 t)
(insert (format "%5d" sum))))))))
文字削除関数の定義例、初学者のよいサンプルになりそう
マーカー
ポイント → バッファ内の絶対アドレス(整数)
マーカー → 任意の文字に付与する目印(マーカーオブジェクト)
(goto-char ..)
などの移動系関数にはマーカーオブジェクトも引数に与えられる
違い
- ポイント位置が指す文字はバッファ内容が変化すれば当然変わる
- マーカーは特定の文字に付与した標識なので、バッファ内容が変化しても変わらない
マーカー利用手順
- マーカーを生成し、
setq
- バッファの所定の位置を
set-marker
- バッファの別の位置で任意の処理(多くはマーカーより手前)
-
set-marker
した位置で任意の処理 - マーカーに
setq nil
してクリア(しないとEmacsの動作が重くなる)
コマンドでマーカー
-
C-SPC
/C-@
→set-mark-command
ナローイング
バッファ内の移動可能な領域を制限し、特定領域だけ編集できるようにする。
ナローイングすると (point-min)
, (point-max)
の値も変化する。
ナローイング作成 → (narrow-to-region pos1 pos2)
ナローイング領域の一時保持 → (save-restriction 領域)
ナローイング解除(最終手段)→ (widen)
/ バッファ全体を処理するという命令なので普通は save-restriction
を使うべき
サンプル
(defun narrowing-test ()
(interactive)
(goto-char (point-min))
(save-restriction ; 処理内のみナローイングを保持
(save-excursion
(search-forward "--END")
(goto-char (match-beginning 0))
(narrow-to-region (point-min) (point)))
(while (not (eobp))
(insert "foo!")
(forward-line 1))))
elisp のデバッグ
printデバッグ
message
と sit-for
でミニバッファに値を表示する
(progn
(setq x 123)
(message "x = [\%d]" x)
(sit-for 3))
Edebug
基本的なフロー
(setq debug-on-error t)
- エラーが発生したらスタックトレースを確認する
- エラー発生関数を
M-x edebug-defun
- 再度実行し関数の動きをトレース
(defun fac (n)
(if (< 0 n)
(* n (fac (1- n)))
1))
; ここで M-x edebug-defun する
▶(fac 3) ; フリンジにデバッグ対象である目印が付く
- 5文字で折り返す関数をデバッグする例
(defun fold-within5 ()
(interactive)
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(move-to-column 5)
(newline 1)
(next-line 1))))
; M-x edebug-defun
C-x b temp
などでバッファを移動して M-x fold-within5
などすると Edebug
が起動する。
aaaaaaaaaaaaaa
bbbbbbbbbbbbbb
; M-x fold-within5 と入力すると Edebug が起動
基本コマンドは以下
- スペースキーでステップ実行
- カーソルを移動して
h
でその箇所まで実行 -
e
でミニバッファでS式を実行 -
q
で終了
データ構造
リスト(list)
基本操作 → https://www.emacswiki.org/emacs/ListModification
連想リスト(alist)
`'(("key1" . "val1") ("key2" . "val2") ("key3" . "val3") ... )`
`'(("foo") ("bar") ("buzz") ... )` ; value がなくても alist
要素の追加 → (cons (list 'key 'val) alist)
連想リストを使った補完関数 → (completing-read プロンプト alist &optional 検査関数 要マッチ 初期入力値)
-
(completion-ignore-case t)
で大文字小文字無視 -
検査関数 →
alist
の単語の中から補完候補を絞りこむための関数-
(lambda (input) string-match "^S" (car input))
→ 先頭Sから始まるのだけに絞る
-
-
alist サンプル
(defun my-completing-buffer-word ()
(save-excursion
(goto-char (point-min)) ; ポイントをバッファ先頭へ
(let (word word-alist)
(while (re-search-forward "\\w+" nil t) ; 英単語を検索し続ける
(setq word (match-string 0))
(if (not (assoc word word-alist)) ; 連想リストを検索
(setq word-alist (cons (list word) word-alist)))) ; 連想リストへ word を登録
(completing-read "Word? " word-alist)))) ; 単語を補完する
プロパティリスト(plist)
key-value 形式を保持するは alist
と同じだが、保存形式にコンスセルを使用しない。
key-value の順にリストにデータを保存する(なので必ず偶数個数のリスト)
; alist
'(("富士山" . 3776) ("雲取山" . 2017) ("高尾山" . 699))
; plist
'("富士山" 3776 "雲取山" 2017 "高尾山" 699)
alist
より変更に弱いのでデータストア的な使用には不適。
plist
は様々なオブジェクトに結び付けて付加的な情報を保持する使用方法を想定(普通は短いplist
をまとめて処理する)
追加 → (setq foo-plist (plist-put foo-plist '塔ノ岳 1491))
取得 → (plist-get foo-plist '塔ノ岳)
キーワードシンボル
:
から始まるシンボル → :foo
, :bar
など
keywordp
が t
を返す
調べるときは keyword symbol
でググる。
(keywordp :foo) ; -> t
(keywordp 'foo) ; -> nil
配列
固定長データタイプ
- 新規作成
(make-vector length initial-value)
(vector &rest 初期値たち)
- 指定した値で全フィル →
(fillarray vec value)
- 値の取得 →
(aref vec 添字)
- 値のセット →
(aset vec 添字 val)
obarray(シンボルテーブル)
- Emacsが内部で利用するシンボルを格納する素数長のVector
- シンボル名(文字列)のハッシュ値からobarrayでの添え字を決定し値を格納する
-
intern
時にシンボルが存在しないときはobarray
に値を追加する -
soft-intern
時は存在しなくてもobarray
を変更しない
-
バッファ
バッファ → 編集作業領域
Emacsの編集作業 → バッファへの操作。
バッファオブジェクト → バッファを管理するEmacs内のデータ
基本操作
作成 → (generate-new-buffer name)
取得 → (get-buffer name)
/ (get-buffer-create name)
削除 → (kill-buffer name-or-obj)
バッファ名の取得 → (buffer-name &optional buf-obj)
ファイル名の取得 → (buffer-file-name buf-obj)
ファイル名からバッファの取得 → (get-file-buffer file-name)
バッファの切り替え
- 編集対象だけ内部切り替え →
(set-buffer name-or-obj)
- 表示画面も含めて切り替え →
(switch-to-buffer name-or-obj)
編集済みフラグ → (buffer-modified-p buf-obj)
編集済みフラグのセット → (set-buffer-modified-p フラグ)
バッファローカル変数
ローカル変数一覧 → (buffer-local-variables &optional target-buffer)
既存変数のローカル化 → (make-local-variable name)
/ 新規作成ではない
恒常ローカル変数の作成 → (make-variable-buffer-local)
/ 全バッファに作成されるバッファローカル変数
- 恒常的バッファローカル変数の初期値 →
set-default
/setq-default
TIPS
一時編集用のバッファ名は先頭にスペースを挿入する → " *temp*"
など
- 補完対象から外れる
テキスト装飾
確認方法
- カーソル上の
face
確認 →(describe-face)
- Emacsで定義されている
face
一覧 →(list-faces-display)
- Emacs上の色一覧 →
(list-colors-display)
装飾手順
- 色指定などの情報を保持するfaceを作成
- faceへスタイル情報(プロパティ)を設定
- faceを「指定した範囲」へ適用
; 1. face の作成
(make-face 'checksheet-face-hide)
; 2. faceへスタイルを設定
(set-face-foreground 'checksheet-face-hide "red") ; 前景を赤
(set-face-background 'checksheet-face-hide "red") ; 背景を赤
; 3. 指定した範囲へ face 適用
(put-text-property 6 10 'face 'checksheet-face-hide)
テキストプロパティ
-
文字装飾
- 文字単位での範囲指定(後から変更不可)
- バッファ内のテキストそのものに与える
plist
- バッファ内のテキストそのものに与える
- テキストデータのメタ情報を変更するのでバッファ内に修正フラグが立つ
- 文字単位での範囲指定(後から変更不可)
-
実は文字列(elisp内の
"..."
)にも付与可能- テキストプロパティが反映された状態でバッファに挿入される
プロパティ付与 → (put-text-property 始点 終点 name value &optional bufobj-or-string)
プロパティ複数付与 → (add-text-property 始点 終点 plist &optional bufobj-or-string)
指定位置にプロパティ取得 → (text-properties-at point &optional bufobj-or-string)
プロパティ削除 → (remove-text-properties 始点 終点 plist &optional bufobj-or-string)
:::message info
テキストプロパティは文字列にメタ情報を与えるということが本質。
バッファ内のプログラムテキストに(装飾とは無関係な)任意のメタ情報を付与して解析関数で利用するなどの使い方も可能。
:::
オーバーレイプロパティ
- 文字の上に被せるように装飾をほどこす
- 装飾範囲は自由(後から変更可能)
- 特定の範囲を覆う
overlay
の作成(make-overlay 始点 終点 &optional buffer 視点移動許可 終点移動許可)
-
overlay
にプロパティを設定する
; 1行名を非表示にする
(save-excursion
(goto-char (point-min))
(let ((ov (make-overlay (point) (progn (end-of-line) (point))))) ; overlay の作成
(overlay-put ov 'invisible t))) ; overlay にプロパティを設定
オーバーレイプロパティ
- 不可視化 →
'(invisible foo)
-
(add-to-invisibility-spec 'foo)
/(remove-from-invisibility-spec 'foo)
で操作可能
-
- カーソル移動不可 →
intangible
- オーバーレイの前後に文字追加 →
before-string
/after-string
- キーマップ設定 →
keymap
- ローカルキーマップ置き換え →
local-map
- 表示するものを指定 →
display
- オーバーレイ内テキスト編集時のフック設定 →
modification-hooks
- オーバーレイの始点・終点の文字列挿入時フック設定 →
insert-in-front-hooks
/insert-behind-hooks
- フェイスの設定 →
face
フェイス(face)
視覚的な属性をまとめて保持するオブジェクト
- 新規作成 →
(make-face シンボル)
- コピー →
(copy-face OLD NEW)
Emacsで使える色
(find-file (expand-file-name "rgb.txt" data-directory))
で表示されるテキストファイルの色名が使える。
ヘルプ
- 標準ドキュメント →
M-x info
- へプルコマンド →
C-h
/<F1>
-
C-h C-h
でどんなヘルプが用意されているかの一覧表示
-
キーバインド
キーバインドについて最初に一読するとよい記事
- キーバインドの設定自体は簡単
- 難しいのは設定する キーマップ を正しく選択すること
モードマップ一覧を表示するコマンド。
C-u M-x apropos-variable RET -mode-map$ RET
モードフックの一覧
C-u M-x apropos-variable RET -mode-hook$ RET
python-modeになったとき、そのバッファで有効なキーバインドを設定
(defun my-add-python-keys ()
(local-set-key (kbd "C-c q") 'shell))
(add-hook 'python-mode-hook 'my-add-python-keys)
ユーザーが自由に利用できるプレフィックスキー
-
<F5>
以降のファンクションキー C-c
-
super
/hyper
キー(s-*
,H-*
)-
super
/hyper
は自分で設定しないと使用不可 -
Xmodmap
など。
-
(setq w32-apps-modifier 'hyper)
(setq w32-lwindow-modifier 'super)
(setq w32-rwindow-modifier 'hyper)
キーマップ
- すべてのバッファで有効 → global-map
- 特定のバッファで有効 → current-local-map
- 特定の{メジャー, マイナー}モードで有効 → 〇〇-mode-map
キーバインドの検索順
マイナーモード(〇〇-mode-map
)
↓
バッファローカルマップ(current-local-map
)
↓
メジャーモードマップ(〇〇-mode-map
)
↓
グローバルマップ(global-map
)
↓
未割り当て(エラー)
より詳細なキーマップ検索
(or (if overriding-terminal-local-map ; 1 -> overriding-terminal-local-map
(find-in overriding-terminal-local-map))
(if overriding-local-map ; 2 -> overriding-local-map
(find-in overriding-local-map)
(or (find-in (get-char-property (point) 'keymap)) ; 3 -> テキストプロパティに付与された keymap
(find-in-any emulation-mode-map-alists) ; 4 ->
(find-in-any minor-mode-overriding-map-alist)
(find-in-any minor-mode-map-alist)
(if (get-text-property (point) 'local-map)
(find-in (get-char-property (point) 'local-map))
(find-in (current-local-map)))))
(find-in (current-global-map)))
-
overriding-terminal-local-map
/ 現在の端末で有効なキーマップ -
overriding-local-map
/ すべてのローカルマップを上書きするキーマップ(使用注意!) - 以下のいずれかのキーマップにキー定義が存在していれば、それを使う
- カーソル位置の文字の
keymap
プロパティ /yasnipet
などで使われている -
emulation-mode-map-alists
中のアクティブなmode-map
-
minor-mode-overriding-map-alist
中のアクティブなmode-map
-
minor-mode-map-alist
中のアクティブなmode-map
/ いわゆるマイナーモード - カーソル位置の文字の
local-map
プロパティ
- カーソル位置の文字の
-
current-local-map
/ カレントバッファで有効なローカルマップ current-global-map
上記のダミー関数に出現する変数は以下参照
引用元: https://tarao.hatenablog.com/entry/20130304/evil_config
キーバインドの割り当て関連
- キー入力指定 →
(kbd "C-m")
/(kbd "<home>")
/(kbd "<f8>")
- キーバインドの置換 →
(define-key key-translation-map (kbd "C-h") (kbd "<DEL>"))
- キーバインドの指定 →
(define-key KEYMAP KEY COMMAND)
-
global-set-key
→(define-key global-map ...)
の糖衣関数 - ローカルバッファ限定
- 設定 →
(local-set-key KEY COMMAND)
- 設定解除 →
(local-unset-key KEY)
- 設定 →
-
- 既存コマンドのキーバインドを別のものコマンドにリマップする
; kill-line 関数を自作の my-kill-line に置き換える
(define-key (current-global-map) [remap kill-line] 'my-kill-line)
local-set-key
の current-local-map
はマイナーモードのキーバインドより後に検索される点を注意。
C-
のような prefix を変更するパッケージ
C-c C-a
みたいなキーバインドを , c , a
のように変更できるとのこと。
Buffer-local だけで有効なキーバインド
Emacs Package Dev Handbook
現代的な関数群を提供するライブラリ
CommonLisp 互換の関数を提供するライブラリ
シーケンス操作を便利に(Emacsバンドル)
alist, ハッシュテーブル, array を便利に(Emacsバンドル)
リスト操作を便利に
文字列操作を便利に
ファイル操作を便利に
ハッシュテーブルを便利に
a-list やハッシュテーブルを便利に inspired by Clojure
エラー処理
ファイル読み込み