背景
以前Emacs でなんでもやりたいと思ってから、zsh メインで使っていたのを eshell メインにした。eshell はデフォルトだと、補完などzsh に比べて劣るところ が多かったため、使えるレベルにするためにいくつか改良を加えた。
環境
- Linux 4.19.2 (arch Linux)
- GNU Emacs 26.1
current buffer でeshell を起動
eshell-on-current-dir
現在のバッファのディレクトリでeshell を起動する関数を作成した。 Eshell - Caiorss.GitHub.io by caiorss を参照にした。
(defun eshell-on-current-dir (&optional arg)
"invoke eshell and cd to current directory"
(interactive "P")
(let ((dir default-directory))
(eshell arg)
(cd dir))
(eshell-emit-prompt)
(goto-char (point-max)))
popwin でopen
上記関数を実行する度に、eshell が1バッファ埋めてしまうのは不便のため、 eshell バッファはpopwin で開くことにした。設定は下記。
(add-to-list 'popwin:special-display-config
'("\\`\\*eshell" :regexp t :dedicated t :position bottom
:height 0.3))
補完強化
デフォルトでは、パスの通っているコマンド、カレントディレクトリ内の ファイル、ディレクトリしか補完できない。そのため、サブコマンドが必要なコマンド、 それ以外の補完候補を取るコマンドの補完をpcomplete で強化した。 下記3つのコマンドの補完関数を作成した。
sudo
systemctl
man
また、pcomplete の使い方に関しては、
- pcomplete (programmable completion) の書き方
- PComplete: Context-Sensitive Completion in Emacs - Mastering Emacs
を参照した。
;;;; sudo completion
(defun pcomplete/sudo ()
"Completion rules for the `sudo' command."
(let ((pcomplete-ignore-case t))
(pcomplete-here (funcall pcomplete-command-completion-function))
(while (pcomplete-here (pcomplete-entries)))))
;;;; systemctl completion
(defcustom pcomplete-systemctl-commands
'("disable" "enable" "status" "start" "restart" "stop" "reenable"
"list-units" "list-unit-files")
"p-completion candidates for `systemctl' main commands"
:type '(repeat (string :tag "systemctl command"))
:group 'pcomplete)
(defvar pcomplete-systemd-units
(split-string
(shell-command-to-string
"(systemctl list-units --all --full --no-legend;systemctl list-unit-files --full --no-legend)|while read -r a b; do echo \" $a\";done;"))
"p-completion candidates for all `systemd' units")
(defvar pcomplete-systemd-user-units
(split-string
(shell-command-to-string
"(systemctl list-units --user --all --full --no-legend;systemctl list-unit-files --user --full --no-legend)|while read -r a b;do echo \" $a\";done;"))
"p-completion candidates for all `systemd' user units")
(defun pcomplete/systemctl ()
"Completion rules for the `systemctl' command."
(pcomplete-here (append pcomplete-systemctl-commands '("--user")))
(cond ((pcomplete-test "--user")
(pcomplete-here pcomplete-systemctl-commands)
(pcomplete-here pcomplete-systemd-user-units))
(t (pcomplete-here pcomplete-systemd-units))))
;;;; man completion
(defvar pcomplete-man-user-commands
(split-string
(shell-command-to-string
"apropos -s 1 .|while read -r a b; do echo \" $a\";done;"))
"p-completion candidates for `man' command")
(defun pcomplete/man ()
"Completion rules for the `man' command."
(pcomplete-here pcomplete-man-user-commands))
bookmarks でお気に入りのディレクトリ登録
zsh を使っていたときは、お気に入りのディレクトリをブックマークして すぐに移動できるプラグインを使用していた。 私はeshell でも同じことがしたく、ディレクトリにも使用できるということで、 emacs 標準のbookmarks 機能を利用することにした。そうしてeshell 上で動く bmk
というコマンドを作った。機能としては
- 登録済みのブックマークにジャンプ
- カレントディレクトリのブックマーク登録
- 特定のブックマークの削除
- ブックマーク一覧の表示
である。使い方は bmk -h
を入力して表示されるヘルプメッセージを参照してほしい。
;;; eshell bookmark command
(defun pcomplete/eshell-mode/bmk ()
"Completion for `bmk'"
(pcomplete-opt "adl")
(pcomplete-here (bookmark-all-names)))
(defun show-bmk-list ()
"Show the bookmark list and each corresponding path"
(let (output-string bmk-name bmk-path (each-format "%-15s|%-5s|%s
"))
(setq output-string (concat (format each-format "Bookmarks" "Type"
"Path") (format "%s
" (make-string 80 ?-))))
(dolist (tmp bookmark-alist)
(setq bmk-name (car tmp))
(setq bmk-path (abbreviate-file-name (assoc-default 'filename tmp)))
(if (file-directory-p bmk-path)
(setq output-string
(concat output-string (format each-format bmk-name "D" bmk-path)))
(setq output-string (concat output-string (format each-format bmk-name "F" bmk-path)))))
output-string))
(defun add-bmk (&optional args)
"add the current path to the bookmark as the name of the first argument"
(let ((name (or (car args)
(file-name-nondirectory
(directory-file-name default-directory)))))
(if name
(progn
(bookmark-set name)
(bookmark-set-filename name (eshell/pwd))
(bookmark-save)
(format "Saved current directory in bookmark %s" name))
(error "You must enter a bookmark name"))))
(defun delete-bmk (args)
"delete bookmark arguments."
(let (output-string)
(if args
(progn (dolist (tmp args)
(bookmark-delete tmp)
(bookmark-save)
(setq output-string
(concat output-string (format "Deleted the bookmark %s
" tmp))))
output-string)
(error "You must enter a bookmark name"))))
(defun cd-bmk (args)
"cd/open the first bookmark argument"
(let ((bookmark (car args)) filename)
(if (setq filename (bookmark-get-filename bookmark))
;; If it points to a directory, change to it.
(if (file-directory-p filename)
(eshell/cd filename)
;; otherwise, just jump to the bookmark
(bookmark-jump bookmark))
(error "%s is not a bookmark" bookmark))))
;; to add bookmark on eshell
(defun bookmark-alternate-buffer-file-name ()
"Return the current buffer's file in a way useful for bookmarks."
;; Abbreviate the path, both so it's shorter and so it's more
;; portable. E.g., the user's home dir might be a different
;; path on different machines, but "~/" will still reach it.
(abbreviate-file-name
(cond
(buffer-file-name buffer-file-name)
((and (boundp 'dired-directory) dired-directory)
(if (stringp dired-directory)
dired-directory
(car dired-directory)))
(default-directory)
(t (error "Buffer not visiting a file or directory")))))
(advice-add 'bookmark-buffer-file-name :override
'bookmark-alternate-buffer-file-name)
(defun eshell/bmk (&rest args)
"Eshell bookmark command to show/add/delete bookmarks.
For usage, execute without arguments."
(eshell-eval-using-options
"bmk" args
'((?a "add" t add
"add current directory as the directory name or BOOKMARK")
(?d "delete" t del
"delete BOOKMARK entry")
(nil "list" nil show
"show the bookmark list")
(?h "help" nil nil "show this usage screen")
;; :show-usage
:usage "BOOKMARK
or: bmk -a [BOOKMARK]
or: bmk -d BOOKMARK ...
or: bmk [OPTION]
Change directory pointed to by BOOKMARK
or bookmark-jump to the BOOKMARK if it is not a directory.
With an option, this can create a bookmark, delete bookmarks
or show the bookmark list.
Without any BOOKMARK and option, this shows the bookmark list.")
(bookmark-maybe-load-default-file)
(setq args (eshell-flatten-list args))
(cond
(show
(show-bmk-list))
(add
(add-bmk args))
(del
(delete-bmk args))
(args
(cd-bmk args))
(t (show-bmk-list)))))
まとめ
eshell の強化方法について載せた。補完に関してはpcomplete で強化はしているが マイナーコマンドの補完ができないといった不便なところもまだまだある。 使ったことはないが、fish の補完を実装できるという、 GitHub - Ambrevar/emacs-fish-completion をいずれ使ってみたい。 また最近は、dired でディレクトリ移動を行っているが、階層の遠いディレクトリ はbookmarks 機能のジャンプを使っている。ブックマークの登録は、そのディレクトリに 行ってから eshell-on-current-dir
でeshell 上で bmk
を使うと便利だ。