Dired 魔改造


dired は emacs の機能の一つだが、キーバインドが通常の emacs キーバインドとかなり違っている。
そのため、私のような記憶力がそれほどない人間にとっては、使用するのが難しいツールという印象であった。
そこで、どうせ覚えられないキーバインドはほぼ全て無効にし、アルファベットを打つと dired-narrow による絞り込みを行うようにしてみた。
元のキーバインドも emacs キーバインドにある程度近くなるように再設定を行なった。

## 準備

必要なパッケージをロードする。

```el:init.el
(require 'cl)
(require 'bind-key)
(require 'dired-x)
(require 'dired+)
(require 'dired-ranger)
(require 'dired-narrow)
```

cl パッケージは loop マクロのためにロードしている。
bind-key はキーバインド登録がわかりやすいので利用しているが、別に使う必要はない。

## dired-narrow による絞り込み

アルファベットや数字を一文字打つと、その文字で絞り込みを開始するようにする。
今回は絞り込みに dired-narrow-fuzzy を利用する。
しかし、元の定義だと最初に打った一文字により dired-narrow-fuzzy を起動させつつ、その文字で絞り込みを行うことができなかった。
（もしかするとやり方によってはできるかもしれないが、私はその方法を知らない）
そのため、dired-narrow のソースコードの一部を拝借して書き換えた。

```el:init.el
  (defun my/dired-narrow-fuzzy (&optional arg)  ;; <- 引数を増やす
    (interactive)
    (my/dired-narrow--internal 'dired-narrow--fuzzy-filter arg))  ;; <- 引数を与える

  (defun my/dired-narrow--internal (filter-function &optional arg)  ;; <- 引数を増やす
    (let ((dired-narrow-buffer (current-buffer))
          (dired-narrow-filter-function filter-function)
          (disable-narrow nil))
      (unwind-protect
          (progn
            (dired-narrow-mode 1)
            (add-to-invisibility-spec :dired-narrow)
            (setq disable-narrow (read-from-minibuffer "Filter: " arg dired-narrow-map))  ;; <- 引数を与える
            ...)
        ...)))
```

そうしてから、キーバインドの登録用関数を作り、全てのアルファベット及び数字について登録を行った。

```el:init.el
(defun register-dired-narrow-fuzzy-key (key)
    (define-key dired-mode-map (read-kbd-macro key) `(lambda () (interactive) (my/dired-narrow-fuzzy ,key))))
    
(loop for c from ?a to ?z do (register-dired-narrow-fuzzy-key (string c)))
(loop for c from ?A to ?Z do (register-dired-narrow-fuzzy-key (string c)))
(loop for c from ?0 to ?9 do (register-dired-narrow-fuzzy-key (string c)))
```

デフォルトだと return を押した際にミニバッファを抜けるだけで、確定するのにもう一度 return を押す必要があり面倒なので、以下の設定を行う。

```el:init.el
;; dired-find-alternate-file 有効化
(put 'dired-find-alternate-file 'disabled nil)

;; return を押したら dired-find-alternate-file を呼ぶ
(setq dired-narrow-exit-action 'dired-find-alternate-file)
```

さらに、dired-narrow による絞り込み中のカーソル移動のキーバインドを設定する。

```el:init.el
(bind-keys :map dired-narrow-map
             ("C-n" . dired-narrow-next-file)
             ("C-p" . dired-narrow-previous-file))
```

dired-narrow 中はフォーカスがミニバッファにあっているため、カーソル位置が非常に見にくくなってしまう。
そのため、私は非アクティブなウィンドウ上のカーソルの形状を box に変更することで視認性を上げている。

```el:init.el
(setq-default cursor-in-non-selected-windows 'box)
```

絞り込みにより候補が1つになった場合に、絞り込みを自動で終了するオプションもある。

```el:init.el
(setq dired-narrow-exit-when-one-left t)
```

## dired-ranger によるコピペ動作

dired-ranger は仮想クリップボードを利用して、普通の文字列のようにファイルをコピー＆ペーストするためのパッケージである。
これらを使うことで、emacs の通常の方法と似た方法でファイルの操作ができる。

```el:init.el
(bind-keys :map dired-mode-map
			("C-w" . dired-ranger-copy)   // 本当は cut したい
			("M-w" . dired-ranger-copy)
			("C-y" . dired-ranger-paste)
			("M-y" . dired-ranger-move))  // コピー元を削除してペースト
```

ただし、このパッケージにはコピーはあるがカットは存在せず、代わりに move (コピー元を削除してペースト)が存在する。
自分で elisp を書いてカットっぽい動作を実現したいところだが面倒なのでやってない。

## マーク

デフォルトだとマークをつける操作と外す操作が別のキーに割り当てられているが、これをトグル操作にすれば一つで済む。
emacs では普通マーク操作は C-SPC なので、そこに割り当てることにした。

```el:init.el
(defun my/dired-toggle-mark (arg)
    "Toggle the current (or next ARG) files."
    (interactive "p")
    (let ((dired-marker-char
           (if (save-excursion (beginning-of-line)
                               (looking-at " "))
               dired-marker-char ?\040)))
      (dired-mark arg)
      (dired-previous-line 1)))
      
(bind-keys :map dired-mode-map
           ("C-SPC" . my/dired-toggle-mark))
```

## 隠しファイルについて

dired-x を利用すると隠しファイル等を非表示にすることができる。
デフォルトではホームディレクトリなどがごちゃごちゃしていてわかりづらいので、隠しファイルは通常は隠しておき、"." が入力された場合は表示するようにした。
dired-x は emacs に標準でインストールされている。

```el:init.el
;; 余計なファイルを非表示に
(setq dired-omit-files "^\\.$\\|^\\.[^\\.].*$\\|\\.elc$\\|^\\.DS_Store$")
(add-hook 'dired-mode-hook (lambda () (dired-omit-mode 1)))

;; "." を押したら隠しファイルも含めて絞り込み開始
(defun my/dired-narrow-fuzzy-dot ()
    (interactive)
    (dired-omit-mode -1)
    (my/dired-narrow--internal 'dired-narrow--fuzzy-filter "."))

;; "." を押したら絞り込み起動
;; C-s を押した場合も絞り込みを起動 (通常 C-s は検索なので)
(bind-keys :map dired-mode-map
           ("." . my/dired-narrow-fuzzy-dot)
           ("C-s" . my/dired-narrow-fuzzy))
```

## C-g (keyboard-quit)

emacs だと C-g を押すことで色々な操作をキャンセルできる。
ここでは、隠しファイルの表示を元に戻し、マークを消し、絞り込みをキャンセルすれば良いだろう。

```el:init.el
(defun my/dired-keyboard-quit ()
    (interactive)
    (dired-omit-mode 1)
    (call-interactively 'dired-unmark-all-marks)
    (call-interactively 'revert-buffer))
    
(bind-keys :map dired-mode-map
           ("C-g" . my/dired-keyboard-quit))
```

## その他の設定

ファイルコピー、削除、リネームのような基本的な機能はどこか適当なキーバインドに割り当てておいた方が良いだろう。
私はこれらを M-c, M-d, M-r にバインドした。
また、return を押した時の動作は揃っている方が良いので、dired-find-alternate-file を return にバインドした。

```el:init.el
(bind-keys :map dired-mode-map
           ("RET" . dired-find-alternate-file)
           ("M-c" . dired-do-copy)
           ("M-d" . dired-do-delete)
           ("M-r" . dired-do-rename))
```

dired+ をロードすることで、ファイルサイズなどの余計な情報が非表示になる。
また、色付け等を細かく制御できる。
私はマークのデフォルトの色付けが見づらいと感じたため、以下のように設定をしている。

```el:init.el
(set-face-attribute 'diredp-flag-mark-line nil
                      :background (face-attribute 'default :background))

(set-face-attribute 'diredp-flag-mark nil 
                      :foreground "forest green"
                      :background (face-attribute 'default :background))

(set-face-attribute 'diredp-dir-name nil
                      :background (face-attribute 'default :background))
```