27
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

EmacsAdvent Calendar 2018

Day 16

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

Last updated at Posted at 2018-12-15

本日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 は物足りないと思った部分に、自分で機能を追加できるので 、とても楽しいです!
皆さんも自分流の関数を作ってみてください!

27
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?