個人的に便利だと思ったEmacsのパッケージと機能
ネット上によくある,Emacsのおすすめパッケージ+αを作ってみた. ここ2〜3年使ってて,よく使うと思ったパッケージなどを説明している. ここで説明していなくても,便利なパッケージはまだまだある(例えば,magitとかsmartrepとか). 説明しているパッケージの都合上,GNU Emacsであればバージョン24以降を対象としている. Vimユーザは,Vimで対応するバンドルを読めばどんな機能の説明をしているのかがほとんど分かると思う.
package
Emacsのパッケージ管理用ツール. Emacsのバージョンが24以降だと,デフォルトでインストールされている. Linuxのapt-getとかyumとか,MacのHomebrewとかMacPortsとかをイメージしてもらうと分かると思う.
基本的にパッケージの管理はこのツールか,el-getというパッケージを利用すると楽になる. 実際に管理する際には,M-x package-list-packagesでコマンドを実行してからインストールしたいパッケージを選択してインストールしたり,アップグレード,アンインストールする. パッケージリストから選択してインストールした例. 次に説明するhelmを実際にインストールする場合は,もっと時間がかかる.
helm
ネット上でよく見かける,おそらく有名なパッケージ. 異なるパッケージで,入力候補用のインタフェースを統一するためのパッケージ. 言葉で説明するよりも,実際に動いているのを見た方が分かりやすいと思う. 初めて使うと何が良いのか全然分からないが,使い慣れるとこれがないとEmacsを使う気がなくなる. Emacsのバージョンが24以降でないと使えず,バージョンが23よりも前だとanythingが利用できる. しかし読み込むのにかなり時間がかかるのが欠点. helmをOnからOffにして比較したのが次のgif.
org mode
表計算,タスク管理,PDF/HTMLへのエクスポート機能などなど,多くの機能を持ったモード. 個人的にはデカすぎだと思う. デフォルトでは,拡張子がorgのファイルをEmacsで開くとこのモードになる. このブログもこのorg-modeで編集してHTMLに変換したものである. helm同様にロード時間が長いので,必要ないのであればロードしないように設定するべき. 次のgifは,org-modeの一部の機能だけ操作している.
auto-complete
自動入力補完用のパッケージ. 他のパッケージを利用しない場合,辞書から候補を出力する. このパッケージを派生したプログラミング用のパッケージもあり, 例えばC++で入力補完するauto-complete-clang-async,Javaで入力補完をするauto-java-completeがある. 次のgifは,auto-completeだけでなく後で説明するyasnippetの候補もauto-completeで選択している.
recentf, recentf-ext
最近開いたファイルの一覧を保存し,その一覧からファイルを開くことができる機能. recentfはデフォルトでインストールされている場合がある. recentf-extはファイルだけでなく,ディレクトリも保存するパッケージで,これはpackageなどを利用してインストールする必要がある.
flymake
最近はflycheckが良いらしいが,全然知らないのでflymakeを説明する. Emacsでプログラミング中に構文チェックを行うパッケージ. 構文エラーがある場合はその行が強調される. なお,構文チェックだけでなく,工夫すればcheckstyleやfindbugsのようなツールで警告も表示できる. 構文エラーの表示方法には色々なパッケージがあるが,flymake-cursorを使っていたのでそれで説明する. flymake-cursorは,構文エラーがある行にカーソルを移動すると,Emacsの下に表示されているミニバッファにエラーの原因が表示される.
yasnippet
スニペットから特定のコードを展開するパッケージ. auto-completeやhelmなどを利用して,スニペットを展開すると使い勝手が良い. 次のgifでは,クラス名や変数名を変更すると,コンストラクタやforのループ変数の名前も変更されている.
dired-mode
ls -lの出力結果から,ディレクトリの移動や名前の変更などができる. Emacs上でディレクトリを開けばこのモードになるので,別に意識することでもないが,重宝したので挙げておく. dired-guess-shell-alist-userの変数を指定すると,dired-mode中で開きたいファイルにカーソルを移動し,"&"を入力すると,指定したプログラムでファイルを開くことができる.
その他
helmとrecentf,recentf-ext,dired-modeを使うと,Emacsがファイルエクスプローラとしてかなり使い勝手がよくなるが, pdfファイルとか,exeやpkg,dmgなどのバイナリファイルはEmacs内で開くよりも別のプログラムから開きたくなってくる. 簡単なのが,explorerやfinderを起動してカレントディレクトリを表示して,そこから開く,あるいは,dired-modeから開く方法である. でもこれだとrecentfの履歴に残らないという問題がある. なので,dired-guess-shell-alist-userの変数を利用して,特定のファイルをEmacsで開こうとすると,Emacsのバッファに表示する代わりに指定したプログラムで開くLispコードを書いた.
;; ファイルエクスプローラでカレントバッファのディレクトリを開く関数を定義する ;; また,指定したファイルは,指定したコマンドで開く ;; Macの場合 (when (eq system-type 'darwin) ;; Openコマンドでカレントディレクトリを開く (defun gui-open-current-directory () (interactive) (shell-command "open . &") ) ;; 指定した拡張子をもつファイルを,Openコマンドで開く (setq dired-guess-shell-alist-user (mapcar #'(lambda(extension) `(,extension "open") ) '("\\.pdf\\'" "\\.dmg\\'" "\\.pkg\\'" )) ) ) ;; Linux (when (eq system-type 'gnu/linux) (defun gui-open-current-directory () (interactive) (shell-command "nemo . &") ;; For Linux MINT ) (setq dired-guess-shell-alist-user '(("\\.pdf" "evince"))) ) ;; Windows (when (eq system-type 'windows-nt) (defun gui-open-current-directory () (interactive) (shell-command "explorer.exe . &") ) ) ;; (global-set-key "\C-c\C-o" 'gui-open-current-directory) ;; ファイルを指定したプログラムで開く ;; プログラムはdired-guess-shell-alist-user変数で指定する (defadvice find-file (around find-file-by-program (file-name &optional wildcards) activate) (unless (memq t (mapcar #'(lambda(element) (let* ((pattern (car element)) (command-name (car (cdr element))) (command (concat command-name " " file-name " &")) ) (when (string-match pattern file-name) ;; 履歴に保存する (recentf-add-file file-name) (call-process-shell-command command nil 0) t ) ) ; end let ) dired-guess-shell-alist-user) ; end mapcar ) ; end memq ;; 本来のfind-fileを実行する ad-do-it ) ; end unless ) ; end defadvice
Vimで対応するバンドル
今回説明したEmacsのパッケージは,Vimでは次のものに対応している.
- NeoBundle
- Unite
- VimOrganizer
- neocomplcache
- Syntastic
- NeoSnippet
- VimFiler
recentfに該当するバンドルは詳しくは知らないが,Uniteのfile_mruかdirectory_mruを使えば同じことができる. EmacsのhelmとVimのUniteは使いやすさに大きく影響するので重要だと思うけど,個人的にはUniteよりもhelmの方が使い勝手がいい気がする. ただ単にVimをほとんど使ってないから,Uniteを使いこなせていないだけかもしれないけど.
Emacs Lispコード
今回紹介したパッケージの設定をまとめた.emacs.d/init.el用のファイル. 最初と途中に全然関係ないコードがあるけど,この記事を書くために便利だった設定である. user-emacs-directoryの変数の値を変更しているので,ポータブル化のための設定をしていますが,ほとんどの人は~/.emacs.d/init.elに設定ファイルを書くと思うのであまり関係ない. ただし,この設定は~/.emacsに書くと正しく動かないので気をつけること. このファイルをコピペして,Emacsを再起動した後に,helm,auto-complete,flymake-cursor, recentf-ext,yasnippetをpackageを利用してインストール後,再度Emacsを再起動すると,今回紹介したパッケージが利用できる. ショートカットとしては,C-c C-rで最近開いたファイルやディレクトリ一覧,C-c C-oで現在のディレクトリをexplorerやfinderで開く (Mac OS Xでのみ動作確認済み).
;;; init.el ;; user-emacs-directory ;; 末尾はパスセパレータにすること (setq user-emacs-directory (file-name-directory load-file-name)) ;; tmpディレクトリをemacsディレクトリの下に置く ;; 末尾はパスセパレータにすること (setq temporary-file-directory (concat user-emacs-directory "tmp/")) (unless (file-exists-p temporary-file-directory) (make-directory temporary-file-directory)) ;; package ;; パッケージをダウンロードするディレクトリを指定 (setq package-user-dir (concat user-emacs-directory "elpa")) (require 'package) ;; リポジトリを追加 (add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/") t) (add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/")) ;; パッケージの初期化 (package-initialize) ;; helm (when (require 'helm-config nil t) (helm-mode t) ;; 最近開いたファイルをショートカットから開けるようにする (global-set-key "\C-c\C-r" 'helm-recentf) (add-hook 'helm-after-initialize-hook #'(lambda () ;; EmacsのデフォルトのC-kの動作に戻す (define-key helm-map (kbd "C-k") 'kill-line) )) ) ;; Org mode ;; テキストファイルをorg-modeで開く (add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode)) (eval-after-load "org" '(progn ;; ショートカットの設定 (define-key org-mode-map "\C-c\C-r" 'helm-recentf) )) ;; org-directoryを設定する. (setq org-directory (expand-file-name "/path/to/agenda/directory")) ;; アジェンダファイルは,org-directory以下の全てのファイル (setq org-agenda-files (list org-directory)) ;; auto-complete (when (require 'auto-complete-config nil t) (ac-config-default) ;; C-n/C-pで選択できる (setq ac-use-menu-map t) ;; 履歴の保存先を変更する (setq ac-comphist-file (concat temporary-file-directory "ac-comphist.dat")) ) ;; recentf, recentf-ext (when (require 'recentf nil t) ;; ファイルの保存先,保存するファイルの名前,最大保存数,クリーンアップを行う時間を設定する (setq recentf-save-file (concat temporary-file-directory "recentf") recentf-exclude '("tmp" "recentf") recentf-max-saved-items 500 recentf-auto-cleanup 10) ;; ディレクトリも履歴に保存するため,extをロードする (require 'recentf-ext nil t) ;; recentfを保存する期間を設定する (run-with-idle-timer 30 t 'recentf-save-list) ;; 最新の履歴ファイルを読み込む (recentf-load-list) ;; recentfを有効にする (recentf-mode t) ) ;; flymake (require 'flymake) (add-hook 'find-file-hook 'flymake-find-file-hook) (setq flymake-start-syntax-check-on-find-file t ; nil flymake-no-changes-timeout 10 flymake-log-level 0 ;; 3 flymake-gui-warnings-enabled nil ) ;; Emacs Lisp用のflymake ;; 詳細は http://www.emacswiki.org/emacs/FlymakeElisp (defun flymake-elisp-init () (unless (string-match "^ " (buffer-name)) (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) (list (expand-file-name invocation-name invocation-directory) (list "-Q" "--batch" "--eval" (prin1-to-string (quote (dolist (file command-line-args-left) (with-temp-buffer (insert-file-contents file) (emacs-lisp-mode) (let ((parse-sexp-ignore-comments t)) (condition-case data (scan-sexps (point-min) (point-max)) (scan-error (goto-char(nth 2 data)) (princ (format "%s:%s: error: Unmatched bracket or quote\n" file (line-number-at-pos))))))) ) ) ) local-file))))) (add-hook 'emacs-lisp-mode-hook (function (lambda () (if buffer-file-name (flymake-mode))))) (push '("\\.el$" flymake-elisp-init) flymake-allowed-file-name-masks) ;; C++ ;; 編集中のcppファイルの上位ディレクトリにMakefileが存在した場合には, ;; そのMakefileファイルを元にmakeを実行. ;; そうでなければg++でsyntaxチェック (defun flymake-get-make-cmdline (source base-dir) ;; make -s -C MakefileDir CHK_SOURCES=path/to/source.cpp SYNTAX_CHECK_MODE=1 LANG=C check-syntaxを実行 (list "make" (list "-s" "-C" (expand-file-name base-dir) (concat "CHK_SOURCES=" source) "SYNTAX_CHECK_MODE=1" "LANG=C" "check-syntax"))) (defun init-c++-flymake-init() (let ((makefile (locate-dominating-file buffer-file-name "Makefile"))) (if makefile (flymake-get-make-cmdline buffer-file-name (file-name-directory makefile)) (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) (list "g++" (list "-Wall" "-Wextra" "-fsyntax-only" local-file)) )) ) ) ; end defun (add-hook 'c++-mode-hook 'flymake-mode-on) (push '("\\.cpp$" init-c++-flymake-init) flymake-allowed-file-name-masks) ;; Show error with popup (when (require 'popup nil t) (defun flymake-popup-err-message () (interactive) (let* ((line-no (flymake-current-line-no)) (line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no)))) (when line-err-info-list (let* ((count (length line-err-info-list)) (menu-item-text nil)) (while (> count 0) (setq menu-item-text (flymake-ler-text (nth (1- count) line-err-info-list))) (let* ((file (flymake-ler-file (nth (1- count) line-err-info-list))) (line (flymake-ler-line (nth (1- count) line-err-info-list)))) (if file (setq menu-item-text (concat menu-item-text " - " file "(" (format "%d" line) ")")))) (setq count (1- count)) (if (> count 0) (setq menu-item-text (concat menu-item-text "\n"))) ) (popup-tip menu-item-text))))) ) ;; yasnippet (eval-after-load "yasnippet" '(progn ;; スニペットを保存したディレクトリを追加する ;; (add-to-list yas-snippet-dirs "") ;; スニペットを読み込む (yas--initialize) )) ;; ファイルエクスプローラでカレントバッファのディレクトリを開く関数を定義する ;; また,指定したファイルは,指定したコマンドで開く ;; Macの場合 (when (eq system-type 'darwin) ;; Openコマンドでカレントディレクトリを開く (defun gui-open-current-directory () (interactive) (shell-command "open . &") ) ;; その他の設定 ;; GNU Emacsにおいて,CommandキーとAltキーを入れ替える (setq ns-command-modifier (quote meta) ns-alternate-modifier (quote super)) ;; ¥の代わりに\を入力する (define-key global-map [?¥] [?\\]) ;; 指定した拡張子をもつファイルを,Openコマンドで開く (setq dired-guess-shell-alist-user (mapcar #'(lambda(extension) `(,extension "open") ) '("\\.pdf\\'" "\\.dmg\\'" "\\.pkg\\'" )) ) ) ;; Linux (when (eq system-type 'gnu/linux) (defun gui-open-current-directory () (interactive) (shell-command "nemo . &") ;; For Linux MINT ) (setq dired-guess-shell-alist-user '(("\\.pdf" "evince"))) ) ;; Windows (when (eq system-type 'windows-nt) (defun gui-open-current-directory () (interactive) (shell-command "explorer.exe . &") ) ) ;; (global-set-key "\C-c\C-o" 'gui-open-current-directory) ;; ファイルを指定したプログラムで開く ;; プログラムはdired-guess-shell-alist-user変数で指定する (defadvice find-file (around find-file-by-program (file-name &optional wildcards) activate) (unless (memq t (mapcar #'(lambda(element) (let* ((pattern (car element)) (command-name (car (cdr element))) (command (concat command-name " " file-name " &")) ) (when (string-match pattern file-name) ;; 履歴に保存する (recentf-add-file file-name) (call-process-shell-command command nil 0) t ) ) ; end let ) dired-guess-shell-alist-user) ; end mapcar ) ; end memq ;; 本来のfind-fileを実行する ad-do-it ) ; end unless ) ; end defadvice