Hydra

EmacsのFunction key設定を公開

あまり役に立つTipsではありませんが、自分の設定を公開します。みなさんの「私の場合は…」というのを教えていただけると嬉しいです。

F1:help-command

F1は、Deaultでいろんなhelp-commadへのprifixとして設定されているのでそのまま使います。which-key.el を導入することで各コマンドのガイドがミニバファーに表示されるので便利です。 Alt Text

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; which-key
(require 'which-key)
(add-hook 'after-init-hook #'which-key-mode)

F2:hydra-compile

一般的には此処に、M-x compileを割り当てている人が多いと思います。私はいろんな作業をmakefaileで自動化しているので目的に応じてコマンドが使えるようにhydraでメニューを設定して割り当てています。

Alt Text

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; compile

(bind-key
 [f2]
 (defhydra hydra-compile (:color red :hint nil)
   "
 🗿 Compile: make _k_  _a_ll  _u_pftp  _m_ove  _b_klog  _g_it  _c_lean   🐾 "
   ("k" my:make-k :exit t)
   ("a" my:make-all :exit t)
   ("u" my:make-upftp :exit t)
   ("m" my:make-move :exit t)
   ("g" my:make-git :exit t)
   ("b" my:make-bklog :exit t)
   ("c" mymake-clean)))

F3:iconify-or-deiconify-frame

emacsclient使用時という条件下でフレームのポップアップ/最小化をtoggleさせます。

;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; iconify-or-deiconify-frame

(bind-key "<f3>" 'iconify-or-deiconify-frame)

F4:Toggle current buffer and scratch buffer.

カレントバッファーとScrtchバッファーとをtoggleさせます。

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Toggle current buffer and `*scratch*` buffer

(defvar toggle-scratch-prev-buffer nil)
(defun toggle-scratch()
  "Toggle current buffer and *scratch* buffer."
  (interactive)
  (if (not (string= "*scratch*" (buffer-name)))
      (progn
	(setq toggle-scratch-prev-buffer (buffer-name))
	(switch-to-buffer "*scratch*"))
    (switch-to-buffer toggle-scratch-prev-buffer)))
(bind-key "<f4>" 'toggle-scratch)

F5:quickrun

ごくたまに perlruby などのミニスクリプトを自作することもあるのでquickrunで簡単に試運転できるようにしています。

view-modeを超えるhydra-pinkyの設定

私のEmacs設定は、プログラマー masasamさんのブログ「 Solist Work Blog」から多くのことを吸収して日々成長しています。 そのブログの中にあるEmacs Pinkyをhydraで解決する の記事からhydra-pinkyの設定を参考にして更に使いやすくカスタマイズしてみました。

Screen Shot

hydra-pinky

hydra-pinky を発動するとミニバッファーに赤文字のメニューがでてきます。 hydra発動中はこの表示が続くのでわかりやすいです。 赤い表示のキーをタイプしている間はいつまでたってもhydraは解除されません。 hydraが終了する条件は、定義された赤いキー以外をタイプするか、青文字の “q”:quit を押した場合です。

つまりhydra-pinkyは、発動中はvew-modeに似た動作をし、そこから抜けると普通の編集モードに戻る…という仕組みです。 複雑なキーバインドを覚える必要もなくGUIでスピーディーにキー操作できるのはとてもありがたいです。 私自身はもともとブラインドタッチを使えないのでEmacs-pinkyの他にもいろいろhydraを活用しています。

HydraでEmacsのキーバインド問題を解消

sequential-command

rubikitch/sequential-command

Emacsを再起動して、C-a C-a C-aすると、行頭にいって、バッファーの頭にいって、元に戻る。 また、C-e C-e C-eすると行頭にいって、バッファーの頭にいって、元に戻るというシンプルな機能ですが、なにげに便利です。

pinkyでは、“a”,“e"キーに割り当てています。

window-toggle-division

Windowの縦分割、横分割をtoggleで切り替えします。

iflipb:バッファー移動

バッファー移動は、next-buffer previous-buffer を使ってもいいのですが、不要なbufferはignoreしてくれるfilipbを使います。 Emacs:タブを使わない究極のバッファー移動

矢印キーの扱い

Emacserの方から見ると邪道だと言われそうですが、私はカーソル移動に矢印キーを使うことも多いです。基本は、h,j,k,lを使うようにしていてもうっかり矢印キーに触れるとhydra-pinkyが消えてしまうので、pinky発動中は矢印関係の操作で消えないようにしています。

設定

;; sequential-command
(use-package sequential-command-config
  :commands sequential-command-setup-keys
  :hook (after-init . sequential-command-setup-keys))

;; other-window-or-split
(bind-key
 "C-q"
 (defun other-window-or-split ()
   "If there is one window, open split window.
If there are two or more windows, it will go to another window."
   (interactive)
   (when (one-window-p)
     (split-window-horizontally))
   (other-window 1)))

;; window-toggle-division
(defun window-toggle-division ()
  "Replace vertical <-> horizontal when divided into two."
  (interactive)
  (unless (= (count-windows 1) 2)
    (error "Not divided into two!"))
  (let ((before-height)
        (other-buf (window-buffer (next-window))))
    (setq before-height (window-height))
    (delete-other-windows)
    (if (= (window-height) before-height)
        (split-window-vertically)
      (split-window-horizontally))
    (other-window 1)
    (switch-to-buffer other-buf)
    (other-window -1)))

;; iflipb
(setq iflipb-wrap-around t)
(setq iflipb-ignore-buffers (list "^[*]" "^magit" "dir]$"))

;; Hydra-pinky
(bind-key [f11] 'hydra-pinky/body)
(key-chord-define-global
 "::"
 (defhydra hydra-pinky (:color red :hint nil)
   "
 :_0_._1_._2_._3_._o_._S_._x_   :_j_._k_._h_._l_._c_._a_._e_._b_._v_._SPC_._w_._s_._/_   :_n_._p_._u_._t_   :_<_-_:_-_>_   :_q_uit"
   ;; window
   ("0" delete-window)
   ("1" delete-other-windows)
   ("2" split-window-below)
   ("3" split-window-right)
   ("o" other-window-or-split)
   ("S" window-swap-states)
   ("x" window-toggle-division)
   ;; page
   ("a" seq-home)
   ("e" seq-end)
   ("j" next-line)
   ("k" previous-line)
   ("l" forward-char)
   ("h" backward-char)
   ("c" recenter-top-bottom)
   ("<down>" next-line)
   ("<up>" previous-line)
   ("<right>" forward-char)
   ("<left>" backward-char)
   ("<C-up>" backward-paragraph)
   ("<C-down>" forward-paragraph)
   ("<C-left>" left-word)
   ("<C-right>" right-word)
   ("b" scroll-down-command)
   ("v" scroll-up-command)
   ("SPC" set-mark-command)
   ("w" avy-goto-word-1)
   ("s" swiper-isearch-region)
   ;; git
   ("n" git-gutter:next-hunk)
   ("p" git-gutter:previous-hunk)
   ("u" git-gutter:popup-hunk)
   ("t" git-gutter:toggle-popup-hunk)
   ;; buffer
   ("/" kill-buffer)
   (":" counsel-switch-buffer)
   ("<" iflipb-previous-buffer)
   (">" iflipb-next-buffer)
   ;; quit
   ("q" nil)))

HydraでEmacsのキーバインド問題を解消する

できるだけデフォルトを上書きしないようにと配慮しながらEmacsのキーバインドを考えるとなるとなかなか難しいですね。うまく割り当てできたとしてもとても覚えて使いこなすのは至難です。その問題を解消して視覚的で使いやすいUIを提供してくれるのが Hydra です。

Hydra はとても奥が深くまだまだ使いこなせないのですが、勉強を兼ねていろいろ設定しましたので備忘録を兼ねてご紹介します。こうすればなお良い…というようなアドバイスを頂けると嬉しいです。

設定の大部分は、hydraの作者であるabo-abo氏のTipsを参考にしました。

Hydra-melpa

Alt Text

emacsのパッケージ管理はいろいろあるようですが、私はMelpaを利用して、必要なパッケージは、init.el に書いて自動インストールされるようにしているのですが、メンテナンス(package-install、update、remove) のためのコマンドをhydraで設定しています。

M-x package-list-packege でリストを表示させてもいいのですが “U” で update すると全てが対象になって思わぬトラブルに遭遇することもあるので、package-utils というパッケージを使っています。

;; use package-utils.el
(defhydra hydra-melpa (:color red :hint nil)
   "
Package: _u_pdate  _r_emove  _i_nstall"
   ("u" package-utils-upgrade-by-name)
   ("r" package-utils-remove-by-name)
   ("i" package-install))

Hydra-git-gutter

Alt Text

emacsの設定ファイル群は、Gitで管理していますので、magit は必要不可欠です。

編集中は、git-gutterが便利なのでまとめてhydraに設定しています。

;; 02_git.el --- 02_git.el
;;; Commentary:
;;; Code:
;; (setq debug-on-error t)

;; magit
(autoload 'magit-status "magit" nil t)
(defadvice magit-status (around magit-fullscreen activate)
  "Magit-status always in fullr screen."
  (window-configuration-to-register :magit-fullscreen)
  ad-do-it
  (delete-other-windows))
(bind-key [f8] 'magit-status)

;; git-gutter
(global-git-gutter-mode t)
(defun git-gutter:toggle-popup-hunk ()
  "Toggle git-gutter hunk window."
  (interactive)
  (if (window-live-p (git-gutter:popup-buffer-window))
      (delete-window (git-gutter:popup-buffer-window))
    (git-gutter:popup-hunk)))

;; Hydra
(bind-key
 "s-g"
 (defhydra hydra-git-gutter (:color red :hint nil)
   "
_m_agit  _b_lame  _d_ispatch  _t_imemachine  |  hunk: _p_revious  _n_ext  _s_tage  _r_evert  pop_u_p  _SPC_:toggle"
   ("m" magit-status :exit t)
   ("b" magit-blame :exit t)
   ("t" git-timemachine :exit t)
   ("d" magit-dispatch :exit t)
   ("p" git-gutter:previous-hunk)
   ("n" git-gutter:next-hunk)
   ("s" git-gutter:stage-hunk)
   ("r" git-gutter:revert-hunk)
   ("u" git-gutter:popup-hunk)
   ("SPC" git-gutter:toggle-popup-hunk)))

;; Local Variables:
;; byte-compile-warnings: (not free-vars callargs)
;; End:
;;; 02_git.el ends here

Hydra-counsel

Alt Text

Hydraで複数のmakeコマンドを自在に使う

ホームページ管理のためにEmacsをワークデスクとして色んな作業をしています。その中で何度も繰り返し行う作業はMakeファイルに書いて限りなく自動化して、それをEmacsから実行しています。

M-x compile

とすると、

Compile command: make -k

とミニバッファーにでます。ここでリターンキーを押すと、makeコマンドが実行されます。

makeファイルの中の特定のブロックを実行したい時、例えば更新されたファイルをサーバーにアップロードしてホームページを更新する時は、make -k を消して make upftp と書き換えてリターンキーを押すとupftpコマンドが実行されるという仕組みです。

通常この使用法でも大した問題はないのですが、compile-commandの内容を書き換えて実行すると、次回にM-x compileコマンドを実行したとき make -k にはならず、先程の書き換えたものがデフォルトに変わってしまうのです。

そこで いろいろな引数でmakeコマンドを使い分けるためい、hydraで設定してみました。

設定

;;; 50_hydra-compile.el --- 50_hydra-compile.el
;;; Commentary:
;;; Code:
;;(setq debug-on-error t)

(bind-key
 [f2]
 (defhydra hydra-compile (:color red :hint nil)
  "
╭─────────────────────────────────────┐
│ 🗿 Compile:
     make:_a_ll  _u_pftp  _m_ove  _b_klog  _g_it
     ---------------------------------
     make:-_k_   _c_lean
└──────────────────────────────────────┘"
   ("a" my:make-all :exit t)
   ("u" my:make-upftp :exit t)
   ("m" my:make-move :exit t)
   ("b" my:make-bklog :exit t)
   ("g" my:make-git :exit t)
   ("k" my:make-default)
   ("c" my:make-clean)))

(defun my:make-default ()
  "Make command default."
  (interactive)
  (setq compile-command "make -k")
  (my:compile))

(defun my:make-upftp ()
  "Make command for upftp."
  (interactive)
  (setq compile-command "make up")
  (my:compile))

(defun my:make-all ()
  "Make command for all."
  (interactive)
  (setq compile-command "make -k && make up")
  (my:compile))

(defun my:make-move ()
  "Make command for move."
  (interactive)
  (setq compile-command "make mv")
  (my:compile))

(defun my:make-bklog ()
  "Make command for bklog."
  (interactive)
  (setq compile-command "make bk")
  (my:compile))

(defun my:make-git ()
  "Make command for git."
  (interactive)
  (setq compile-command "make git")
  (my:compile))

(defun my:make-clean ()
  "Make command for clean."
  (interactive)
  (setq compile-command "make clean")
  (my:compile))

(defun my:compile ()
  "Restore compile command after recompile."
  (interactive)
  (recompile)
  (setq compile-command "make -k"))

;; Local Variables:
;; byte-compile-warnings: (not free-vars callargs)
;; End:
;;; 50_hydra-compile ends here

更に使いやすくする

compileshell-command を実行すると結果のログが表示されます。その都度閉じるのが面倒なので popwin で表示させるようにします。popwin windowは “C-g” で閉じることができます。emacsでは何かと “C-g” を使うことが多いので、私はキーハックアプリで command_L に割り当ててワンキーで実行できるようにしています。

Hydraを使ったview-mode設定

以前にご紹介した下記の記事を、hydra.elを使って更に使いやすく工夫してみた。

まずは、設定ファイルの全容を貼り付けます。

;; Change mode-line color in view-mode
(use-package viewer)
(setq viewer-modeline-color-view "dark red")
(viewer-change-modeline-color-setup)

;; View-mode key map
(use-package keys-in-view-mode)
(with-eval-after-load 'view
  (bind-key "h" 'backward-char view-mode-map)
  (bind-key "j" 'next-line view-mode-map)
  (bind-key "k" 'previous-line view-mode-map)
  (bind-key "l" 'forward-char view-mode-map)
  (bind-key "b" 'scroll-down view-mode-map)
  (bind-key "0" 'beginning-of-line view-mode-map)
  (bind-key "e" 'end-of-line view-mode-map)
  (bind-key "g" 'goto-line view-mode-map)
  (bind-key "G" 'View-goto-percent view-mode-map)
  (bind-key "d" 'view-kill-whole-line view-mode-map)
  (bind-key "m" 'magit-status view-mode-map)
  (bind-key "v" 'vc-diff view-mode-map)
  (bind-key "," 'howm-remember view-mode-map)
  (bind-key "u" 'view-undo view-mode-map)
  (bind-key "r" 'view-redo view-mode-map)
  (bind-key "%" 'view-jump-brace view-mode-map)
  (bind-key "w" 'forward-word+1 view-mode-map)
  (bind-key "W" 'backward-word view-mode-map)
  (bind-key "o" 'new-line-below-insert view-mode-map)
  (bind-key "y" 'copy-region-as-kill view-mode-map)
  (bind-key "Y" 'view-copy-line view-mode-map)
  (bind-key "p" 'paste-at-cursor view-mode-map)
  (bind-key "P" 'paste-at-down-line view-mode-map)
  (bind-key "x" 'view-del-char view-mode-map)
  (bind-key "X" 'view-backward-kill-line view-mode-map)
  (bind-key "D" 'kill-end-of-line view-mode-map)
  (bind-key "a" 'forward-char-to-insert view-mode-map)
  (bind-key "A" 'end-of-line-to-insert view-mode-map)
  (bind-key "i" 'view-mode view-mode-map)
  (bind-key "I" 'beginning-of-line-to-insert view-mode-map)
  (bind-key "L" 'pdf-preview-buffer view-mode-map)
  (bind-key ":" 'view-mode view-mode-map)
  (key-chord-define view-mode-map "gg" 'View-goto-line))

;; Add hl-line-mode to view-mode
(when (functionp 'hl-line-mode)
  (add-hook 'view-mode-hook '(lambda () (hl-line-mode 1)))
  (defadvice view-mode-disable (after disable-hl-line-mode activate)
    (hl-line-mode -1)))

;; Customize to open file in view-mode
(defun neo-open-file (full-path &optional arg)
  (neo-global--select-mru-window arg)
  (view-file full-path))

;; PDF-preview
  (with-eval-after-load 'view
    (use-package pdf-preview)
    (setq pdf-preview-font-rescale-factor 1.2)
    (when (eq system-type 'darwin)
      (setq pdf-preview-preview-command "open -a Preview.app"))
    (when (eq system-type 'gnu/linux)
      (setq pdf-preview-preview-command "evince"))
    (setq ps-line-number t))

;; hydra-view-mode
(defhydra hydra-view-mode (:hint nil :exit t)
  "
^Insert mode^        ^Move^            ^Delete^            ^Editing^             ^Utl
^^^^^^------------------------------------------------------------------------------------------
_a_: after cursor  _spc_: next page    _d_: one line       _u_: undo  _r_: redo    _m_: magit-status
_i_: at the cursor   _b_: prev page    _x_: one char       _y_: copy region      _v_: vc-diff
_A_: at end line     _w_: next word    _X_: to begin line  _Y_: copy line        _n_: neotree
_I_: at begin line   _W_: prev word    _D_: to end line    _p_: paste at cursor  _L_: PDF preview
_o_: new line below  _%_: match parens                   _P_: paste down line  _,_: remember
"
  ;; Insert mode
  ("a" forward-char-to-insert)
  ("i" view-mode)
  ("A" end-of-line-to-insert)
  ("I" beginning-of-line-to-insert)
  ("o" new-line-below-insert)
  ;; Move
  ("b" scroll-down)
  ("spc" scroll-up)
  ("w" forward-word+1)
  ("W" backward-word)
  ("%" view-jump-brace)
  ;; Delete
  ("d" view-kill-whole-line)
  ("x" view-del-char)
  ("X" view-backward-kill-line)
  ("D" kill-end-of-line)
  ;; Editing
  ("u" view-undo)
  ("r" view-redo)
  ("y" copy-region-as-kill)
  ("Y" view-copy-line)
  ("p" paste-at-cursor)
  ("P" paste-at-down-line)
  ;; Utl
  ("m" magit-status)
  ("v" vc-diff)
  ("n" neotree-toggle)
  ("," howm-remember)
  ("L" pdf-preview-buffer)
  ("q" nil "leave"))

(define-key view-mode-map "." 'hydra-view-mode/body)

hydraで対話式にコマンドを使えるようにする

Alt Text

hydraをミニメニューとして使う

hydraは、関連するコマンドを複数定義して、 それらのコマンドを連続して簡単に呼び出せるようにするツールです。基本的にはsmartrepの機能と同じですが、パラメーターによっていろいろ応用できるようなので試してみました。メニュー内容をエコーエリアに表示させることもできるのでGUIとしても優れています。