Emacs
AdventCalendar
emacs-lisp
EmacsDay 16

Emacs で自作関数を使ってみる

本日2本目のEmacs関係のアドベントカレンダー投稿です。

1つ目は Emacs での Web 開発に関する投稿 でしたので、よければ見てください!


はじめに

Emacs を使っていると、細かい設定に手を入れたくなることが多々あります。

そんなときに「サクッと自分で自作設定(関数)が書ける」のが Emacs のいいところかなーと思ってます。

というわけで今回は、今年に入って自分で Emacs を使っているうちに作ってみた関数の紹介です。

(すでに Qiita 等で紹介済みのものが多いので、そちらもご参照いただければ)


自作関数(Utility系)

ここにはコードの一部分だけ載せますが、大体は GitHubQiita で公開してるので、詳細はそちらで!


finder-current-dir-open

finder-current-dir-open.gif

コードを書いているときでも、たまーに「この assets ディレクトリが見たいなー」な瞬間があります。

そんなときに Emacs からパッと finder で開けると便利です(See: EmacsからFinderを開く方法

;; open current directory

(defun finder-current-dir-open()
(interactive)
(shell-command "open .")) ;; 補足あり

;; open selected directory
(defun finder-open(dirname)
(interactive "DDirectoryName:")
(shell-command (concat "open " dirname)))

追記: 適切な外部アプリでファイルを開いてくれる package もいくつかあります。

quick-preview: Quick preview using GNOME sushi, gloobus or quick look

counsel-osx-app: Launch OSX applications via ivy interface


shell-command について


Emacs では shell のサブプロセスにアクセスして、shell command(touch とか open とか)を実行することができます。

(shell-command (/path/to/shell.sh)) のようにすれば、自作 ShellScript を実行することもできるので、非常に便利です(cf. Emacsからの安全なシェルコマンド実行


instant-maximized-window

instant-maximized-window.gif

分割した画面を一時的に拡大できます。

広い画面でファイルを見たいときに便利です(See: Emacsでwindowをおっきくしちゃう

;; 画面のが最大化されている or NOTの状態を保持

(defvar is-window-maximized nil)

;; 1. 最大化されている場合
;; -> `balance-windows` で画面のバランスを調整
;; 2. 最大化されていない場合
;; -> `maximize-window` で画面を最大化

(defun window-temp-maximize ()
(interactive) ;; 補足あり
(progn
(if is-window-maximized
(balance-windows)
(maximize-window))
(setq is-window-maximized (not is-window-maximized))))


interactive について


関数内に interactive と記述すると、M-x でその関数を起動させることができるようになります。

さらに interactive P などのように PD を書くことで、ユーザの入力やディレクトリ情報を読み込んだりしてくれます(cf. Interactive-Codes


stopwatch

stopwatch.png pausing.png

syohex さんの emacs-mode-line-timer を大いに参考にさせていただき、最初に書いてみた自作 package です。

modeline に操作可能な stopwatch を表示させます(See: GitHub/stopwatch

(defvar current-state 'working

"Stopwatch statement flag, working or pausing")

(defun stopwatch-start (&optional minutes)
(interactive)
(when stopwatch--timer
(error "Already start stopwatch!!"))
(unless minutes
(setq minutes 0))
(setq current-state 'working)
(stopwatch--set-remainder-second minutes)
(setq stopwatch--timer (run-with-timer 0 1 'stopwatch-timer--tick)))


gitlab-mr-from-commit

gitlab-rm-from-commit.gif

「Emacsで現在見ている行を変更したPRを開けるようにした」 という記事に触発されて作りました。

上の記事は GitHub を前提にしていたので、GitLab 用で作ってみました(See: GitLabでもCommit Hashから該当MRを見つけたい

;; vc-annotate 実行後に該当行で M-x gitlab-open-mr

;; shell-command で外部のスクリプト gitlab-mr-from-commit を実行している
(defun gitlab-open-mr ()
(interactive)
(let* ((rev-at-line (vc-annotate-extract-revision-at-line))
(rev (car rev-at-line)))
(shell-command (mapconcat 'shell-quote-argument
(list "gitlab-mr-from-commit" rev) " "))))


buffer-flash

buffer-flash.gif

buffer.....光らせたくない? 僕は光らせたい(See: Emacsでbufferをパッと光らせたい

(defun flash-foreground ()

"Flash foreground."
(interactive)
(let ((orig-fg (face-attribute 'default :foreground)))
(set-face-foreground 'default "black")
(run-with-idle-timer 0.1 nil ;; 補足あり
(lambda (fg) (set-face-foreground 'default fg))
orig-fg)))


run-with-idle-timer について


(run-with-idle-timer secs repeat function &rest args) の形で使います。

Emacs が sec 秒間 idle 状態にあるときに発火する関数を設定することができます。今回は repeatnil なので1度だけの発火ですが、複数回発火させることもできます。


copy-current-git-path

Git の root ディレクトリからの相対パスをクリップボードにコピーします。

同僚に「この部分のコードです」とかチャットで path を伝えるときに便利(See: GitHub/copy-current-git-path

(defun get-git-filepath ()

"Get relative current path from git dir."
(let* ((path buffer-file-name)
(root (file-truename (vc-git-root path)))
(filepath-from-git-root (file-relative-name path root)))
(message filepath-from-git-root)))

(defun copy-current-git-path ()
"Copy relative current path from git dir to clipboard."
(interactive)
(let ((path (get-git-filepath))) ;; 補足あり
(when path
(message "copied path: %s" path)
(kill-new path)))) ;; 補足あり


let について


いわゆるローカル変数を定義してくれる便利な関数。

上記の場合は path = get-git-filepaht と同じ。

亜種に let* があるが、こちらは「前に定義したローカル変数の影響を受ける」(cf. Emacs Lisp基礎文法最速マスター


kill-new について


いわゆるクリップボードに変数を格納してくれる便利な関数。

上記の場合は path をクリップボードにコピーしている。


faker

faker.png

現在開いているファイルを別ディレクトリに クローン することができます。

また、編集したクローンファイルはオリジナルと置き換えることもできます(See: GitHub/faker

;; (1)補足あり

(define-minor-mode faker-minor-mode
"minor mode for faker"
:init-value nil
:global nil
:lighter " Faker")

(defun faker--genuine-to-fake()
"Create the fake file from the genuine file."
(let* ((old-file-path (buffer-file-name))
(new-file-path (faker--fake-file-name buffer-file-name)))
(write-file new-file-path t)
(faker-minor-mode 1) ;; (2)補足あり
(message "Create fake file!")
(sit-for 0.5)))

(defun faker()
"Create the genuine or fake file."
(interactive)
(if (bound-and-true-p faker-minor-mode) ;; (3)補足あり
(faker--fake-to-genuine)
(faker--genuine-to-fake)))


Minor Mode について


faker では独自の minor mode を定義し、現在の faker-minor-mode の ON/OFF 状態によってコマンドの挙動を変えています。

(1): minor mode の定義(cf. How to Make an Emacs Minor Mode

(2): minor mode の有効化

(3): minor mode の ON/OFF のチェック


how-many

how-many.png

「この単語ってプロジェクト内でどれくらい使ってるんだっけ?」と調べたい時が多々ありますよね、僕はあります。

(See: Emacsで単語の使用頻度をパッと見る

(defun how-many()

"Show how many times the word at point is used in the project."
(interactive)
(let* ((string (thing-at-point 'word)))
(popup-tip ;; 補足あり
(shell-command-to-string
(concat "git grep " string " | wc -l")))))


popup-tip について


popup.el内で定義されている関数。

引数に string をとり、いわゆるツールチップとして表示してくれる。とても便利。


自作関数(Ruby on Rails)


spec-jump

spec-jump.gif

Rails をいじっていると、現在開いているファイルの spec ファイルにジャンプしたい時が多々あります。

できるだけタイピングしたくないので作りました(See: Emacsで該当のspecにパッと飛びたい

(defun spec-jump()

"Jump from original to spec, spec to original."
(interactive)
(let* ((filepath (buffer-file-name)))
(if (spec-jump--is-spec-file filepath) ;; 補足あり
(spec-jump--spec-to-original filepath)
(spec-jump--original-to-spec filepath))))


if について


今回のケースでは、spec-jump--is-spec-file で現在開いているファイルが spec file か否かを判断しています。

spec file ならば spec-jump--spec-to-original を発火させ、それ以外なら spec-jump--original-to-spec を発火させることで、ファイルを交互に行き来させることが可能になっています。


rubocop-fix-file

rubocop-fix-file.gif

一々 Linter 先生の言うことを解釈してコードを修正するのは面倒なんで、自動でやらせちゃいましょう。

ということで、作ってみました(See: GitHub/rubocop-fix-file.el

(defun rubocop-fix-file ()

"Execute rubocop auto correct."
(interactive)
(let* ((filepath buffer-file-name)
(current-filename (file-name-nondirectory filepath)))
(call-process-shell-command
(mapconcat 'shell-quote-argument
(list "rubocop" "-a" "--format" "emacs" current-filename) " ") nil 0)
(message "rubocop fixing current file....")))

......と思ったらすでに 「Rails プロジェクトでファイル保存時に自動的に rubocop --autocorrect してもらおう」 でグレートな対策が書かれているので、是非ご参照ください!


message について


mini buffer に文字列を出力できる組み込み関数です。

例えば C-x C-s で保存するときに無言でセーブされると「え....?保存された?」って不安になりますよね。そういうときに、 (message "file was saved!") と表示してやればわかりやすくなりますよ!(cf. Mode Lineを光らせる


rspec-on-iterm

rspec-on-iterm.gif

Rspec は Terminal 上で実行する派だったのですが、一々 spec/controllers/hoge_controller_spec.rb:15 とか Terminal に入力するのは面倒でした。なので、カーソルのある行のテストを いい感じに Terminal で実行(またはクリップボードにコピー)するために作りました(See: GitHub/rspec-on-iterm

;; See: https://stackoverflow.com/a/23960720/8888451

;; Git の root dir からの相対パスを取得
(defun git-dir-relative-path ()
(let* ((path buffer-file-name)
(root (file-truename (vc-git-root path)))
(filename (file-name-nondirectory path))
(filename-length (length filename))
(chunk (file-relative-name path root)))
(substring chunk 0 (- (length chunk) filename-length))))

;; 実行することで
;; "cd /Users/blue0513/rails/; RAILS_ENV=test spec spec/controllers/hoge_controller_spec.rb:15
;; という文字列を得る
(defun executable-rspec-string()
(let* ((path buffer-file-name)
(filename (file-name-nondirectory path)) ;; 補足あり
(line-number (number-to-string (line-number-at-pos)))
(target-dir (file-truename (vc-git-root path))))
(concat "cd " target-dir ";"
"RAILS_ENV=test rspec " (git-dir-path) filename ":" line-number)))


file-name-nondirectory について


要するに Path からディレクトリやファイル名を返してくれる関数。

これに関しては 「GNU Emacs Lisp Reference Manual」めずらしく わかりやすい。

(file-name-directory "lewis/foo")

;; "lewis/"

(file-name-nondirectory "lewis/foo")
;; "foo"

(file-name-sans-extension "foo.lose.c")
;; "foo.lose"


自作関数(おまけ)

かっこいい名前の package を開発したかっただけ。


icy-tail

icy-tail.gif

巨大なファイルを tail したい時、ありますよね。

tail.el を参考に練習がてら作ってみました。まだまだ改善の余地ありです(See: GitHub/icy-tail


hell-fire

ディレクトリのファイルを 警告なしで削除します

決して実行しないでください。泣きます(See: GitHub/hell-fire


おわりに

Emacs は物足りないと思った部分に、自分で機能を追加できるので 、とても楽しいです!

皆さんも自分流の関数を作ってみてください!