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

本日2本目のEmacs関係のアドベントカレンダー投稿です。
1つ目は [Emacs での Web 開発に関する投稿](https://www.m3tech.blog/entry/emacs-web-service) でしたので、よければ見てください！

## はじめに

Emacs を使っていると、細かい設定に手を入れたくなることが多々あります。
そんなときに「サクッと自分で自作設定（関数）が書ける」のが Emacs のいいところかなーと思ってます。

というわけで今回は、今年に入って自分で Emacs を使っているうちに作ってみた関数の紹介です。
（すでに [Qiita](https://qiita.com/blue0513) 等で紹介済みのものが多いので、そちらもご参照いただければ）


## 自作関数（Utility系）

ここにはコードの一部分だけ載せますが、大体は [GitHub](https://github.com/blue0513) や [Qiita](https://qiita.com/blue0513) で公開してるので、詳細はそちらで！

### finder-current-dir-open

![finder-current-dir-open.gif](https://qiita-image-store.s3.amazonaws.com/0/144707/9571bc82-67de-8fda-6f61-6d88df9c0c20.gif)

コードを書いているときでも、たまーに「この assets ディレクトリが見たいなー」な瞬間があります。
そんなときに Emacs からパッと finder で開けると便利です（See: [EmacsからFinderを開く方法](https://qiita.com/blue0513/items/8031893295cd609b935e)）

```elisp
;; 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](https://github.com/myuhe/quick-preview.el): Quick preview using GNOME sushi, gloobus or quick look
[counsel-osx-app](https://github.com/d12frosted/counsel-osx-app): Launch OSX applications via ivy interface

> [shell-command](https://www.emacswiki.org/emacs/ExecuteExternalCommand) について

Emacs では shell のサブプロセスにアクセスして、shell command（touch とか open とか）を実行することができます。
`(shell-command (/path/to/shell.sh))` のようにすれば、自作 ShellScript を実行することもできるので、非常に便利です（cf. [Emacsからの安全なシェルコマンド実行](https://qiita.com/tadsan/items/17d32514b81f1e8f208a)）

### instant-maximized-window

![instant-maximized-window.gif](https://qiita-image-store.s3.amazonaws.com/0/144707/d63ebdbe-c4c8-a79f-5802-86d99060456e.gif)

分割した画面を一時的に拡大できます。
広い画面でファイルを見たいときに便利です（See: [Emacsでwindowをおっきくしちゃう](https://qiita.com/blue0513/items/a7dd88fbac8b22caa38c)）

```elisp
;; 画面のが最大化されている 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](https://www.emacswiki.org/emacs/InteractiveFunction) について

関数内に `interactive` と記述すると、`M-x` でその関数を起動させることができるようになります。
さらに `interactive P` などのように `P` や `D` を書くことで、ユーザの入力やディレクトリ情報を読み込んだりしてくれます（cf. [Interactive-Codes](https://www.gnu.org/software/emacs/manual/html_node/elisp/Interactive-Codes.html)）

### stopwatch

<img width="314" alt="stopwatch.png" src="https://qiita-image-store.s3.amazonaws.com/0/144707/a7dba501-2d73-4bbd-db3b-3fa92076519b.png"> <img width="317" alt="pausing.png" src="https://qiita-image-store.s3.amazonaws.com/0/144707/d882defd-c70b-fd2e-b9fc-e122413967ef.png">

syohex さんの [emacs-mode-line-timer](https://github.com/syohex/emacs-mode-line-timer) を大いに参考にさせていただき、最初に書いてみた自作 package です。
modeline に操作可能な stopwatch を表示させます（See: [GitHub/stopwatch](https://github.com/blue0513/stopwatch)）

```elisp
(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](https://qiita-image-store.s3.amazonaws.com/0/144707/9e421d87-7f5f-f704-f8be-dfafeb5a30ed.gif)


[「Emacsで現在見ている行を変更したPRを開けるようにした」](https://blog.shibayu36.org/entry/2018/05/08/193000) という記事に触発されて作りました。
上の記事は GitHub を前提にしていたので、GitLab 用で作ってみました（See: [GitLabでもCommit Hashから該当MRを見つけたい](https://qiita.com/blue0513/items/dd8cfd8ad80af3b61b69)）

```elisp
;; 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](https://qiita-image-store.s3.amazonaws.com/0/144707/50e1025c-b815-16da-4760-08c864345865.gif)


buffer.....光らせたくない？ 僕は光らせたい（See: [Emacsでbufferをパッと光らせたい](https://qiita.com/blue0513/items/2c824ea7642d8827da9d)）

```elisp
(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](https://www.emacswiki.org/emacs/IdleTimers#toc2) について

`(run-with-idle-timer secs repeat function &rest args)` の形で使います。
Emacs が `sec` 秒間 idle 状態にあるときに発火する関数を設定することができます。今回は `repeat` が `nil` なので1度だけの発火ですが、複数回発火させることもできます。

### copy-current-git-path

Git の root ディレクトリからの相対パスをクリップボードにコピーします。
同僚に「この部分のコードです」とかチャットで path を伝えるときに便利（See: [GitHub/copy-current-git-path](https://github.com/blue0513/copy-current-git-path)）

```elisp
(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](https://www.gnu.org/software/emacs/manual/html_node/elisp/Local-Variables.html) について

いわゆるローカル変数を定義してくれる便利な関数。
上記の場合は `path = get-git-filepaht` と同じ。
亜種に `let*` があるが、こちらは「前に定義したローカル変数の影響を受ける」（cf. [Emacs Lisp基礎文法最速マスター](http://d.hatena.ne.jp/rubikitch/20100201/elispsyntax)）

> [kill-new](https://www.gnu.org/software/emacs/manual/html_node/eintr/kill_002dnew-function.html) について

いわゆるクリップボードに変数を格納してくれる便利な関数。
上記の場合は `path` をクリップボードにコピーしている。

### faker

![faker.png](https://qiita-image-store.s3.amazonaws.com/0/144707/a9239d55-fb6d-26a1-b3d8-565b456a917f.png)

現在開いているファイルを別ディレクトリに **クローン** することができます。
また、編集したクローンファイルはオリジナルと置き換えることもできます（See: [GitHub/faker](https://github.com/blue0513/faker.el)）

```elisp
;; (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](https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Minor-Modes.html) について

`faker` では独自の `minor mode` を定義し、現在の `faker-minor-mode` の ON/OFF 状態によってコマンドの挙動を変えています。
(1): `minor mode` の定義（cf. [How to Make an Emacs Minor Mode](https://nullprogram.com/blog/2013/02/06/)）
(2): `minor mode` の有効化
(3): `minor mode` の ON/OFF のチェック

### how-many

<img width="492" alt="how-many.png" src="https://qiita-image-store.s3.amazonaws.com/0/144707/7b41de6a-441b-9550-5a31-3e58cb124e38.png">

「この単語ってプロジェクト内でどれくらい使ってるんだっけ？」と調べたい時が多々ありますよね、僕はあります。
（See: [Emacsで単語の使用頻度をパッと見る](https://qiita.com/blue0513/items/9e1138e9543cb69217da)）

```elisp
(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](https://www.emacswiki.org/emacs/PopUp) について

[`popup.el`](https://github.com/auto-complete/popup-el)内で定義されている関数。
引数に string をとり、いわゆるツールチップとして表示してくれる。とても便利。

## 自作関数（Ruby on Rails）

### spec-jump

![spec-jump.gif](https://qiita-image-store.s3.amazonaws.com/0/144707/0982846f-95f8-fb98-902d-cef3f1b9d379.gif)


Rails をいじっていると、現在開いているファイルの spec ファイルにジャンプしたい時が多々あります。
できるだけタイピングしたくないので作りました（See: [Emacsで該当のspecにパッと飛びたい](https://qiita.com/blue0513/items/230cf820d158391eb87b)）

```elisp
(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](https://www.gnu.org/software/emacs/manual/html_node/elisp/Conditionals.html) について

今回のケースでは、`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](https://qiita-image-store.s3.amazonaws.com/0/144707/726b170a-bfb4-ac5b-21cb-b654cd74e635.gif)


一々 Linter 先生の言うことを解釈してコードを修正するのは面倒なんで、自動でやらせちゃいましょう。
ということで、作ってみました（See: [GitHub/rubocop-fix-file.el](https://github.com/blue0513/rubocop-fix-file.el)）

```elisp
(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 してもらおう」](https://qiita.com/motchang/items/2d754dc6d6beb8a4863b) でグレートな対策が書かれているので、是非ご参照ください！

> [message](http://ergoemacs.org/emacs/elisp_printing.html) について

mini buffer に文字列を出力できる組み込み関数です。
例えば `C-x C-s` で保存するときに無言でセーブされると「え....？保存された？」って不安になりますよね。そういうときに、 `(message "file was saved!")` と表示してやればわかりやすくなりますよ！（cf. [Mode Lineを光らせる](https://qiita.com/blue0513/items/99476f4ae51f17600636#mode-line%E3%82%92%E5%85%89%E3%82%89%E3%81%9B%E3%82%8B)）

### rspec-on-iterm

![rspec-on-iterm.gif](https://qiita-image-store.s3.amazonaws.com/0/144707/e4d4ec71-a694-e974-3725-486641c2bca6.gif)


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

```elisp
;; 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](https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Name-Components.html) について

要するに Path からディレクトリやファイル名を返してくれる関数。
これに関しては [「GNU Emacs Lisp Reference Manual」](https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Name-Components.html) が ~~めずらしく~~ わかりやすい。

```elisp
(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](https://qiita-image-store.s3.amazonaws.com/0/144707/59776007-bca2-0ce6-870e-4208f51dac20.gif)

巨大なファイルを `tail` したい時、ありますよね。
[tail.el](https://www.emacswiki.org/emacs/tail.el) を参考に練習がてら作ってみました。まだまだ改善の余地ありです（See: [GitHub/icy-tail](https://github.com/blue0513/icy-tail.el)）

### hell-fire

ディレクトリのファイルを **警告なしで削除します** 。
決して実行しないでください。泣きます（See: [GitHub/hell-fire](https://github.com/blue0513/hell-fire)）

## おわりに

Emacs は物足りないと思った部分に、**自分で機能を追加できるので** 、とても楽しいです！
皆さんも自分流の関数を作ってみてください！