本日2本目のEmacs関係のアドベントカレンダー投稿です。
1つ目は Emacs での Web 開発に関する投稿 でしたので、よければ見てください!
はじめに
Emacs を使っていると、細かい設定に手を入れたくなることが多々あります。
そんなときに「サクッと自分で自作設定(関数)が書ける」のが Emacs のいいところかなーと思ってます。
というわけで今回は、今年に入って自分で Emacs を使っているうちに作ってみた関数の紹介です。
(すでに Qiita 等で紹介済みのものが多いので、そちらもご参照いただければ)
自作関数(Utility系)
ここにはコードの一部分だけ載せますが、大体は GitHub や Qiita で公開してるので、詳細はそちらで!
finder-current-dir-open
コードを書いているときでも、たまーに「この 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
分割した画面を一時的に拡大できます。
広い画面でファイルを見たいときに便利です(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
などのように P
や D
を書くことで、ユーザの入力やディレクトリ情報を読み込んだりしてくれます(cf. Interactive-Codes)
stopwatch
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
「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.....光らせたくない? 僕は光らせたい(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 状態にあるときに発火する関数を設定することができます。今回は repeat
が nil
なので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
現在開いているファイルを別ディレクトリに クローン することができます。
また、編集したクローンファイルはオリジナルと置き換えることもできます(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
「この単語ってプロジェクト内でどれくらい使ってるんだっけ?」と調べたい時が多々ありますよね、僕はあります。
(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
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
一々 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 は 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)))
要するに 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
巨大なファイルを tail
したい時、ありますよね。
tail.el を参考に練習がてら作ってみました。まだまだ改善の余地ありです(See: GitHub/icy-tail)
hell-fire
ディレクトリのファイルを 警告なしで削除します 。
決して実行しないでください。泣きます(See: GitHub/hell-fire)
おわりに
Emacs は物足りないと思った部分に、自分で機能を追加できるので 、とても楽しいです!
皆さんも自分流の関数を作ってみてください!